Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: experimental single tab run mode for component testing #23104

Merged
merged 33 commits into from
Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2ecdd91
revive logic to run CT in a single tab
lmiller1990 Jul 27, 2022
7d52188
add feature flag: experimentalSingleTabRunMode
lmiller1990 Jul 27, 2022
1b473df
remove log
lmiller1990 Jul 27, 2022
918997e
Merge remote-tracking branch 'origin/develop' into lmiller/experiment…
lmiller1990 Aug 1, 2022
b5574b5
Merge remote-tracking branch 'origin/develop' into lmiller/experiment…
lmiller1990 Aug 4, 2022
5c760d4
reset browser state between tests
lmiller1990 Aug 4, 2022
ff28d69
document single tab run mode experiment;
lmiller1990 Aug 4, 2022
48c637d
add system test for experimental run mode
lmiller1990 Aug 4, 2022
7cf8da9
fix snapshots
lmiller1990 Aug 4, 2022
3adcb40
use more simple project for testing
lmiller1990 Aug 4, 2022
adfd7ce
additional guard;
lmiller1990 Aug 4, 2022
ca5a16c
fix test
lmiller1990 Aug 4, 2022
58efbac
Merge remote-tracking branch 'origin/develop' into lmiller/experiment…
lmiller1990 Aug 4, 2022
9861034
resolve conflicts
lmiller1990 Aug 4, 2022
0dcfe74
Apply suggestions from code review
lmiller1990 Aug 9, 2022
4c99b4d
update test with retries
lmiller1990 Aug 9, 2022
84f0d8a
destroy aut after each spec
lmiller1990 Aug 9, 2022
33b9d43
merge in develop
lmiller1990 Aug 10, 2022
805bcdc
update snapshot
lmiller1990 Aug 10, 2022
646857d
fix types
lmiller1990 Aug 10, 2022
3747bc8
add experiment flag error
lmiller1990 Aug 10, 2022
1d1df2e
add warning when using experimental flag with e2e
lmiller1990 Aug 10, 2022
81a3a0c
build binaries for experimentalSingleTabRunMode feature
lmiller1990 Aug 10, 2022
293a0f4
build binaries take 2
lmiller1990 Aug 10, 2022
d7aa638
make error message more open ended
lmiller1990 Aug 11, 2022
e83e56c
destroy AUT later in run mode lifecycle
lmiller1990 Aug 11, 2022
bc430a9
add additional assertion around experimental flag
lmiller1990 Aug 11, 2022
7453063
simplify error
lmiller1990 Aug 11, 2022
cb609e6
merge develop and resolve conficts
lmiller1990 Aug 15, 2022
3cb9ab3
remove test code from production code
lmiller1990 Aug 15, 2022
785e1ae
merge in origin and resolve conflicts
lmiller1990 Aug 15, 2022
80cd48a
merge in develop
lmiller1990 Aug 15, 2022
56bf522
fix conflict
lmiller1990 Aug 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3055,6 +3055,11 @@ declare namespace Cypress {
interface ComponentConfigOptions<ComponentDevServerOpts = any> extends Omit<CoreConfigOptions, 'baseUrl' | 'experimentalSessionAndOrigin'> {
devServer: DevServerFn<ComponentDevServerOpts> | DevServerConfigOptions
devServerConfig?: ComponentDevServerOpts
/**
* Enables running all specs in a single tab in run mode for faster run mode execution.
* @default false
*/
experimentalSingleTabRunMode?: boolean
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/config/__snapshots__/index.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
"experimentalSessionAndOrigin": false,
"experimentalModifyObstructiveThirdPartyCode": false,
"experimentalSourceRewriting": false,
"experimentalSingleTabRunMode": false,
"fileServerFolder": "",
"fixturesFolder": "cypress/fixtures",
"excludeSpecPattern": "*.hot-update.js",
Expand Down Expand Up @@ -118,6 +119,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
"experimentalSessionAndOrigin": false,
"experimentalModifyObstructiveThirdPartyCode": false,
"experimentalSourceRewriting": false,
"experimentalSingleTabRunMode": false,
"fileServerFolder": "",
"fixturesFolder": "cypress/fixtures",
"excludeSpecPattern": "*.hot-update.js",
Expand Down Expand Up @@ -194,6 +196,7 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
"experimentalSessionAndOrigin",
"experimentalModifyObstructiveThirdPartyCode",
"experimentalSourceRewriting",
"experimentalSingleTabRunMode",
"fileServerFolder",
"fixturesFolder",
"excludeSpecPattern",
Expand Down
7 changes: 7 additions & 0 deletions packages/config/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ const resolvedOptions: Array<ResolvedConfigOption> = [
isExperimental: true,
canUpdateDuringTestTime: false,
requireRestartOnChange: 'server',
}, {
Copy link
Member

@emilyrohrbough emilyrohrbough Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our config isn't in the greatest state given we don't validate against this list.. As is, e2e testers could set these even if the server didn't honor it. it'd be better to add this to the list of breaking e2e test configuration changes that are lower in this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Also a good chance to solicit feedback from CT users. What do you think of this?

image

name: 'experimentalSingleTabRunMode',
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
canUpdateDuringTestTime: false,
requireRestartOnChange: 'server',
}, {
name: 'fileServerFolder',
defaultValue: '',
Expand Down
1 change: 1 addition & 0 deletions packages/launchpad/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default defineConfig({
videoCompression: false, // turn off video compression for CI
},
component: {
experimentalSingleTabRunMode: true,
supportFile: 'cypress/component/support/index.ts',
devServer: {
bundler: 'vite',
Expand Down
2 changes: 2 additions & 0 deletions packages/server/lib/experiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const _summaries: StringValues = {
experimentalInteractiveRunEvents: 'Allows listening to the `before:run`, `after:run`, `before:spec`, and `after:spec` events in the plugins file during interactive mode.',
experimentalSessionAndOrigin: 'Enables cross-origin and improved session support, including the `cy.origin` and `cy.session` commands.',
experimentalSourceRewriting: 'Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm.',
experimentalSingleTabRunMode: 'Runs all component specs in a single tab, trading performance for spec isolation.',
experimentalStudio: 'Generate and save commands directly to your test suite by interacting with your app as an end user would.',
}

Expand All @@ -72,6 +73,7 @@ const _names: StringValues = {
experimentalFetchPolyfill: 'Fetch Polyfill',
experimentalInteractiveRunEvents: 'Interactive Mode Run Events',
experimentalSessionAndOrigin: 'Cross-origin and Session',
experimentalSingleTabRunMode: 'Single Tab Run Mode',
experimentalSourceRewriting: 'Improved Source Rewriting',
experimentalStudio: 'Studio',
}
Expand Down
38 changes: 27 additions & 11 deletions packages/server/lib/modes/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,12 @@ module.exports = {

displayRunStarting,

navigateToNextSpec (spec) {
debug('navigating to next spec')

return openProject.changeUrlToSpec(spec)
},

exitEarly (err) {
debug('set early exit error: %s', err.stack)

Expand Down Expand Up @@ -1105,6 +1111,16 @@ module.exports = {
return this.currentSetScreenshotMetadata(data)
}

if (options.experimentalSingleTabRunMode && options.testingType === 'component' && !options.isFirstSpec) {
// reset browser state to match e2e behavior when opening/closing a new tab
return openProject.resetBrowserState().then(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We reset the state here, much like we do between tabs, so this minimizes the chance of negatively impacting spec isolation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this doesn't matter since this is experimental, but if something goes wrong, this loses the test retries for CT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh true, this seems like a problem - something we will definitely need to highlight in the notes, unless we can find a fix prior to releasing this experiment (if that's even acceptable, will need to update the tech brief with this drawback... good point).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually @emilyrohrbough I don't get it - I tried to use retries but it works okay: 4c99b4d#diff-ff7a203eea50ba8d34ac184c54310e07e4f279447a923ba9bec878e643b9e289R699-R700

Is there another way to configuring this or an edge case that I'm missing?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retries are only for the initial browser launch right? I'm not sure we'd need retries for when we're navigating in this new way

// If we do not launch the browser,
// we tell it that we are ready
// to receive the next spec
return this.navigateToNextSpec(options.spec)
})
}

const wait = () => {
debug('waiting for socket to connect and browser to launch...')

Expand Down Expand Up @@ -1244,17 +1260,15 @@ module.exports = {
}
}

// Close the browser if the environment variable is set to do so
// if (process.env.CYPRESS_INTERNAL_FORCE_BROWSER_RELAUNCH) {
// debug('attempting to close the browser')
// await openProject.closeBrowser()
// } else {
debug('attempting to close the browser tab')
await openProject.resetBrowserTabsForNextTest(shouldKeepTabOpen)
// }
if (!config.experimentalSingleTabRunMode) {
debug('attempting to close the browser tab')

await openProject.resetBrowserTabsForNextTest(shouldKeepTabOpen)

debug('resetting server state')
openProject.projectBase.server.reset()
debug('resetting server state')

openProject.projectBase.server.reset()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might be dropping the server reset(). does it make single tab move if we reset this regardless of the flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean "move"?

Also I don't exactly remember why we reset here at all - I'm not sure how this impacts single tab run mode uniquely, I'm guessing anything that impacts regular run mode will also impact single tab run mode.

}

if (videoExists && !skippedSpec && endVideoCapture && !videoCaptureFailed) {
const ffmpegChaptersConfig = videoCapture.generateFfmpegChaptersConfig(results.tests)
Expand Down Expand Up @@ -1496,8 +1510,10 @@ module.exports = {
socketId: options.socketId,
webSecurity: options.webSecurity,
projectRoot: options.projectRoot,
testingType: options.testingType,
isFirstSpec,
experimentalSingleTabRunMode: config.experimentalSingleTabRunMode,
shouldLaunchNewTab: !isFirstSpec, // !process.env.CYPRESS_INTERNAL_FORCE_BROWSER_RELAUNCH && !isFirstSpec,
// TODO(tim): investigate the socket disconnect
}),
})
})
Expand Down
15 changes: 15 additions & 0 deletions packages/server/lib/open_project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,21 @@ export class OpenProject {
this.closeOpenProjectAndBrowsers()
}

changeUrlToSpec (spec: Cypress.Spec) {
if (!this.projectBase) {
return
}

const newSpecUrl = getSpecUrl({
projectRoot: this.projectBase.projectRoot,
spec,
})

debug(`New url is ${newSpecUrl}`)

this.projectBase.server._socket.changeToUrl(newSpecUrl)
}

// close existing open project if it exists, for example
// if you are switching from CT to E2E or vice versa.
// used by launchpad
Expand Down
4 changes: 4 additions & 0 deletions packages/server/lib/socket-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,4 +612,8 @@ export class SocketBase {
close () {
return this._io?.close()
}

changeToUrl (url: string) {
return this.toRunner('change:to:url', url)
}
}
141 changes: 141 additions & 0 deletions system-tests/__snapshots__/component_testing_spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,3 +564,144 @@ exports['React major versions with Webpack executes all of the tests for React v


`

exports['experimentalSingleTabRunMode / executes all specs in a single tab'] = `
We detected that you have versions of dependencies that are not officially supported:

- \`webpack\`. Expected >=4.0.0 || >=5.0.0 but dependency was not found.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean our system test isn't in a great state if we arn't testing against our supported versions?

Copy link
Contributor Author

@lmiller1990 lmiller1990 Aug 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's happening is we are falling back to the bundled version of webpack that we ship with Cypress. In open mode we actually show a warning in the launchpad. In run mode, we don't do that - we print the warning in the CLI. In that sense, the system tests are doing exactly what they should - warning us we are missing a dependency that we should install.

We could add webpack to the package.json for this test project, if we'd like to get rid of this warning. The warning should probably be "If you're experiencing problems, install the missing dependency or downgrade to a support version and restart Cypress.".

In most cases, people won't get to this point, since they'll use launchpad to configure everything, and we have a screen to check and help them install the correct dependencies.


If you're experiencing problems, downgrade dependencies and restart Cypress.

28 modules

====================================================================================================

(Run Starting)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 2 found (fails.spec.js, foo.spec.js) │
│ Searched: **/*.spec.js │
│ Experiments: experimentalSingleTabRunMode=true │
└────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────

Running: fails.spec.js (1 of 2)


simple passing spec
1) fails
2) fails again


0 passing
2 failing

1) simple passing spec
fails:

AssertionError: expected 1 to equal 2
+ expected - actual

-1
+2

[stack trace lines]

2) simple passing spec
fails again:

AssertionError: expected 1 to equal 3
+ expected - actual

-1
+3

[stack trace lines]




(Results)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 2 │
│ Passing: 0 │
│ Failing: 2 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 2 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: fails.spec.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘


(Screenshots)

- /XXX/XXX/XXX/cypress/screenshots/fails.spec.js/simple passing spec -- fails (fai (1280x720)
led).png
- /XXX/XXX/XXX/cypress/screenshots/fails.spec.js/simple passing spec -- fails agai (1280x720)
n (failed).png


(Video)

- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/fails.spec.js.mp4 (X second)


────────────────────────────────────────────────────────────────────────────────────────────────────

Running: foo.spec.js (2 of 2)


component
✓ has spec with window same as app window


1 passing



component

(Results)

┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: foo.spec.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘


(Video)

- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/foo.spec.js.mp4 (X second)


====================================================================================================

(Run Finished)


Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✖ fails.spec.js XX:XX 2 - 2 - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ foo.spec.js XX:XX 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✖ 1 of 2 failed (50%) XX:XX 3 1 2 - -


`
18 changes: 18 additions & 0 deletions system-tests/test/component_testing_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,21 @@ describe(`React major versions with Webpack`, function () {
})
}
})

describe('experimentalSingleTabRunMode', function () {
systemTests.setup()

systemTests.it('executes all specs in a single tab', {
Copy link
Member

@emilyrohrbough emilyrohrbough Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming no, but with our current system tests, this there any way to benchmark the performance gains from this flag?

Also, how interactive are the tests in this system test? just curious if it'd highlight potential test isolation leaks or if they are pretty "simple" tests that should be reliable regardless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are fairly simple at this point - I don't think the will reveal and isolation issues, but having some that do sure would be nice. I'm not entirely sure of the best example of one, though - something that modifies window.top? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for performance, and tracking regressions, no... I think we should look into this, probably as a fast follow. One idea I had was we could just run app-component-tests twice using the system test infrastructure, once with the experimental flag and once without, then print a report and make some assertions. It might cost us a bit of extra $, will need to look into this more closely.

I think there's some talk of using Graphana in #team-e2e-testing but this seems like it won't land anytime soon.

I'll try to add something small in here to at least verify it is faster with the experimental flag.

project: 'component-tests',
testingType: 'component',
spec: '**/*.spec.js',
browser: 'chrome',
config: {
component: {
experimentalSingleTabRunMode: true,
},
},
snapshot: true,
expectedExitCode: 2,
})
})