-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Cypress Studio for Cypress 10 (#23544)
* chore: wire up Cypress Studio (#23413) * wip * wip * wip - spike * more wip [skip ci] * update style * fix ts * move types around * extract types * lint * fixing tests * fix component test * skip some tests * do not error on experimentalStudio flag * add studio controls placeholder * fixing tests * revert * revert changes * rename store * rename method * remove comment * refactor * correctly feature flag studio * simplify code * simplify code * lift check into useEventManager * correctly hide create studio prompt based on flag; * remove superfulous css * rename variables * fix bugs * wip * unskip tests * unskip more tests * fix a bug in the assertion API * fix bug in assertions [skip ci] * wip - bugs [skip ci] * feat: add experimentalStudio flag back (#23506) Co-authored-by: astone123 <[email protected]> * chore: Add Studio UI to Cypress 10 (#23537) * wip * wip * wip - spike * more wip [skip ci] * update style * fix ts * move types around * extract types * lint * fixing tests * fix component test * skip some tests * do not error on experimentalStudio flag * add studio controls placeholder * fixing tests * revert * revert changes * rename store * rename method * remove comment * refactor * correctly feature flag studio * chore: wip add barebones studio modals * simplify code * simplify code * lift check into useEventManager * correctly hide create studio prompt based on flag; * remove superfulous css * chore: style studio toolbar * chore: misc feedback * chore: remove studio store prop * chore: studio URL prompt and other changes * update component * chore: UI styling and remove studio init modal * chore: revert unnecessary changes * chore: fix types * chore: fix some tests, minor refactor (#23545) * fix test * fix test * add noHelp link to StandardModal Co-authored-by: Lachlan Miller <[email protected]> * test: studio e2e tests (#23546) * add basic e2e test * add some e2e tests for studio and a note on limitations * additional spec * add more tests, refactor helper * fix bug in studio * remove test code * chore: UI feedback * fix race condition * update tests * rename test * improve types in reporter * remove dead code * improve tests * merge tests into one spec * chore: Cap instruction modal width; exit studio mode when new spec is chosen * chore: Only render studio error when test has failed; add test for studioEnabled * correctly check if command is studio or not * improve specs and hopefully reduce flake * communicate studio state from app->reporter * receive studio save state validity from app * fix test * improve test coverage * fix external link Co-authored-by: astone123 <[email protected]>
- Loading branch information
1 parent
6a614c3
commit 72b8a65
Showing
81 changed files
with
2,461 additions
and
2,744 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## Cypress Studio Tests | ||
|
||
These are the tests for the Cypress Studio feature. [Learn more here](https://docs.cypress.io/guides/references/cypress-studio). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export function launchStudio () { | ||
cy.scaffoldProject('experimental-studio') | ||
cy.openProject('experimental-studio') | ||
cy.startAppServer('e2e') | ||
cy.visitApp() | ||
cy.get(`[data-cy-row="spec.cy.js"]`).click() | ||
|
||
cy.waitForSpecToFinish() | ||
|
||
// Should not show "Studio Commands" until we've started a new Studio session. | ||
cy.get('[data-cy="hook-name-studio commands"]').should('not.exist') | ||
|
||
cy | ||
.contains('visits a basic html page') | ||
.closest('.runnable-wrapper') | ||
.realHover() | ||
.findByTestId('launch-studio') | ||
.click() | ||
|
||
// Studio re-executes spec before waiting for commands - wait for the spec to finish executing. | ||
cy.waitForSpecToFinish() | ||
|
||
cy.get('[data-cy="hook-name-studio commands"]').should('exist') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
import { launchStudio } from './helper' | ||
|
||
describe('Cypress Studio', () => { | ||
it('updates an existing test with a click action', () => { | ||
function addStudioClick (initialCount: number) { | ||
cy.getAutIframe().within(() => { | ||
cy.get('p').contains(`Count is ${initialCount}`) | ||
|
||
// (1) First Studio action - get | ||
cy.get('#increment') | ||
|
||
// (2) Second Studio action - click | ||
.realClick().then(() => { | ||
cy.get('p').contains(`Count is ${initialCount + 1}`) | ||
}) | ||
}) | ||
} | ||
|
||
launchStudio() | ||
|
||
cy.get('button').contains('Save Commands').should('be.disabled') | ||
|
||
addStudioClick(0) | ||
|
||
cy.get('button').contains('Save Commands').should('not.be.disabled') | ||
|
||
cy.get('.studio-command-remove').click() | ||
|
||
cy.get('button').contains('Save Commands').should('be.disabled') | ||
|
||
addStudioClick(1) | ||
|
||
cy.get('button').contains('Save Commands').should('not.be.disabled') | ||
|
||
cy.get('[data-cy="hook-name-studio commands"]').closest('.hook-studio').within(() => { | ||
cy.get('.command').should('have.length', 2) | ||
// (1) Get Command | ||
cy.get('.command-name-get').should('contain.text', '#increment') | ||
|
||
// (2) Click Command | ||
cy.get('.command-name-click').should('contain.text', 'click') | ||
}) | ||
|
||
cy.get('button').contains('Save Commands').click() | ||
|
||
cy.withCtx(async (ctx) => { | ||
const spec = await ctx.actions.file.readFileInProject('cypress/e2e/spec.cy.js') | ||
|
||
expect(spec.trim().replace(/\r/g, '')).to.eq(` | ||
it('visits a basic html page', () => { | ||
cy.visit('cypress/e2e/index.html') | ||
/* ==== Generated with Cypress Studio ==== */ | ||
cy.get('#increment').click(); | ||
/* ==== End Cypress Studio ==== */ | ||
})`.trim()) | ||
}) | ||
|
||
// Studio re-executes the test after writing it file. | ||
// It should pass | ||
cy.waitForSpecToFinish({ passCount: 1 }) | ||
|
||
// Assert the commands we input via Studio are executed. | ||
cy.get('.command-name-visit').within(() => { | ||
cy.contains('visit') | ||
cy.contains('cypress/e2e/index.html') | ||
}) | ||
|
||
cy.get('.command-name-get').within(() => { | ||
cy.contains('get') | ||
cy.contains('#increment') | ||
}) | ||
|
||
cy.get('.command-name-click').within(() => { | ||
cy.contains('click') | ||
}) | ||
}) | ||
|
||
it('writes a test with all kinds of assertions', () => { | ||
function assertStudioHookCount (num: number) { | ||
cy.get('[data-cy="hook-name-studio commands"]').closest('.hook-studio').within(() => { | ||
cy.get('.command').should('have.length', num) | ||
}) | ||
} | ||
|
||
launchStudio() | ||
|
||
cy.getAutIframe().within(() => { | ||
cy.get('#increment').rightclick().then(() => { | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('be enabled').realClick() | ||
}) | ||
}) | ||
|
||
assertStudioHookCount(2) | ||
|
||
cy.getAutIframe().within(() => { | ||
cy.get('#increment').rightclick().then(() => { | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('be visible').realClick() | ||
}) | ||
}) | ||
|
||
assertStudioHookCount(4) | ||
|
||
cy.getAutIframe().within(() => { | ||
cy.get('#increment').rightclick().then(() => { | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('have text').realHover() | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('Increment').realClick() | ||
}) | ||
}) | ||
|
||
assertStudioHookCount(6) | ||
|
||
cy.getAutIframe().within(() => { | ||
cy.get('#increment').rightclick().then(() => { | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('have id').realHover() | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('increment').realClick() | ||
}) | ||
}) | ||
|
||
assertStudioHookCount(8) | ||
|
||
cy.getAutIframe().within(() => { | ||
cy.get('#increment').rightclick().then(() => { | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('have attr').realHover() | ||
cy.get('.__cypress-studio-assertions-menu').shadow().contains('onclick').realClick() | ||
}) | ||
}) | ||
|
||
assertStudioHookCount(10) | ||
|
||
cy.get('[data-cy="hook-name-studio commands"]').closest('.hook-studio').within(() => { | ||
// 10 Commands - 5 assertions, each is a child of the subject's `cy.get` | ||
cy.get('.command').should('have.length', 10) | ||
|
||
// 5x cy.get Commands | ||
cy.get('.command-name-get').should('have.length', 5) | ||
|
||
// 5x Assertion Commands | ||
cy.get('.command-name-assert').should('have.length', 5) | ||
|
||
// (1) Assert Enabled | ||
cy.get('.command-name-assert').should('contain.text', 'expect <button#increment> to be enabled') | ||
|
||
// (2) Assert Visible | ||
cy.get('.command-name-assert').should('contain.text', 'expect <button#increment> to be visible') | ||
|
||
// (3) Assert Text | ||
cy.get('.command-name-assert').should('contain.text', 'expect <button#increment> to have text Increment') | ||
|
||
// (4) Assert Id | ||
cy.get('.command-name-assert').should('contain.text', 'expect <button#increment> to have id increment') | ||
|
||
// (5) Assert Attr | ||
cy.get('.command-name-assert').should('contain.text', 'expect <button#increment> to have attr onclick with the value increment()') | ||
}) | ||
|
||
cy.get('button').contains('Save Commands').click() | ||
|
||
cy.withCtx(async (ctx) => { | ||
const spec = await ctx.actions.file.readFileInProject('cypress/e2e/spec.cy.js') | ||
|
||
expect(spec.trim().replace(/\r/g, '')).to.eq(` | ||
it('visits a basic html page', () => { | ||
cy.visit('cypress/e2e/index.html') | ||
/* ==== Generated with Cypress Studio ==== */ | ||
cy.get('#increment').should('be.enabled'); | ||
cy.get('#increment').should('be.visible'); | ||
cy.get('#increment').should('have.text', 'Increment'); | ||
cy.get('#increment').should('have.id', 'increment'); | ||
cy.get('#increment').should('have.attr', 'onclick', 'increment()'); | ||
/* ==== End Cypress Studio ==== */ | ||
})` | ||
.trim()) | ||
}) | ||
}) | ||
|
||
it('creates a test using Studio, but cancels and does not write to file', () => { | ||
launchStudio() | ||
|
||
cy.getAutIframe().within(() => { | ||
cy.get('p').contains('Count is 0') | ||
|
||
// (1) First Studio action - get | ||
cy.get('#increment') | ||
|
||
// (2) Second Studio action - click | ||
.realClick().then(() => { | ||
cy.get('p').contains('Count is 1') | ||
}) | ||
}) | ||
|
||
cy.get('[data-cy="hook-name-studio commands"]').closest('.hook-studio').within(() => { | ||
cy.get('.command').should('have.length', 2) | ||
// (1) Get Command | ||
cy.get('.command-name-get').should('contain.text', '#increment') | ||
|
||
// (2) Click Command | ||
cy.get('.command-name-click').should('contain.text', 'click') | ||
}) | ||
|
||
cy.get('[data-cy="hook-name-studio commands"]').should('exist') | ||
|
||
cy.get('a').contains('Cancel').click() | ||
|
||
// Cyprss re-runs after you cancel Studio. | ||
// Original spec should pass | ||
cy.waitForSpecToFinish({ passCount: 1 }) | ||
|
||
cy.get('.command').should('have.length', 1) | ||
|
||
// Assert the spec was executed without any new commands. | ||
cy.get('.command-name-visit').within(() => { | ||
cy.contains('visit') | ||
cy.contains('cypress/e2e/index.html') | ||
}) | ||
|
||
cy.get('[data-cy="hook-name-studio commands"]').should('not.exist') | ||
|
||
cy.withCtx(async (ctx) => { | ||
const spec = await ctx.actions.file.readFileInProject('cypress/e2e/spec.cy.js') | ||
|
||
// No change, since we cancelled. | ||
expect(spec.trim().replace(/\r/g, '')).to.eq(` | ||
it('visits a basic html page', () => { | ||
cy.visit('cypress/e2e/index.html') | ||
})`.trim()) | ||
}) | ||
}) | ||
|
||
// TODO: Can we somehow do the "Create Test" workflow within Cypress in Cypress? | ||
it('creates a brand new test', () => { | ||
cy.scaffoldProject('experimental-studio') | ||
cy.openProject('experimental-studio') | ||
cy.startAppServer('e2e') | ||
cy.visitApp() | ||
cy.get(`[title="empty.cy.js"]`).should('be.visible').click() | ||
|
||
cy.waitForSpecToFinish() | ||
|
||
cy.contains('Create test with Cypress Studio').click() | ||
cy.get('[data-cy="aut-url"]').as('urlPrompt') | ||
|
||
cy.get('@urlPrompt').within(() => { | ||
cy.contains('Continue ➜').should('be.disabled') | ||
}) | ||
|
||
cy.get('@urlPrompt').type('http://localhost:4455/cypress/e2e/index.html') | ||
|
||
cy.get('@urlPrompt').within(() => { | ||
cy.contains('Continue ➜').should('not.be.disabled') | ||
cy.contains('Cancel').click() | ||
}) | ||
|
||
// TODO: Can we somehow do the "Create Test" workflow within Cypress in Cypress? | ||
// If we hit "Continue" here, it updates the domain (as expected) but since we are | ||
// Cypress in Cypress, it redirects us the the spec page, which is not what normally | ||
// would happen in production. | ||
}) | ||
}) |
Oops, something went wrong.