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

* revive logic to run CT in a single tab

* add feature flag: experimentalSingleTabRunMode

* remove log

* reset browser state between tests

* document single tab run mode experiment;

* add system test for experimental run mode

* fix snapshots

* use more simple project for testing

* additional guard;

* fix test

* Apply suggestions from code review

Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com>

* destroy aut after each spec

* update snapshot

* fix types

* add experiment flag error

* add warning when using experimental flag with e2e

* build binaries for experimentalSingleTabRunMode feature

* build binaries take 2

* make error message more open ended

* destroy AUT later in run mode lifecycle

* add additional assertion around experimental flag

* simplify error

* remove test code from production code

Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com>
This commit is contained in:
Lachlan Miller
2022-08-16 10:44:14 +10:00
committed by GitHub
parent f7dce39876
commit 91beb9012c
36 changed files with 551 additions and 46 deletions

View File

@@ -27,8 +27,6 @@ mainBuildFilters: &mainBuildFilters
branches:
only:
- develop
- reapply-state-refactor
- fix-or-skip-flaky-tests
# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
@@ -46,6 +44,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ "lmiller/experimental-single-tab-component-testing", << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>

View File

@@ -3062,6 +3062,11 @@ declare namespace Cypress {
interface ComponentConfigOptions<ComponentDevServerOpts = any> extends Omit<CoreConfigOptions, 'baseUrl' | 'experimentalSessionAndOrigin'> {
devServer: DevServerFn<ComponentDevServerOpts> | DevServerConfigOptions
devServerConfig?: ComponentDevServerOpts
/**
* Runs all component specs in a single tab, trading spec isolation for faster run mode execution.
* @default false
*/
experimentalSingleTabRunMode?: boolean
}
/**

View File

@@ -10,6 +10,17 @@ export const StubWebsocket = new Proxy<Socket>(Object.create(null), {
},
})
beforeEach(() => {
if (!window.top?.getEventManager) {
throw Error('Could not find `window.top.getEventManager`. Expected `getEventManager` to be defined.')
}
// this is always undefined, since we only define it when
// running CT with a project that sets `experimentalSingleTabRunMode: true`
// @ts-ignore - dynamically defined during tests using
expect(window.top.getEventManager().autDestroyedCount).to.be.undefined
})
// Event manager with Cypress driver dependencies stubbed out
// Useful for component testing
export const createEventManager = () => {

View File

@@ -1,3 +1,10 @@
import '@packages/frontend-shared/cypress/e2e/support/e2eSupport'
import 'cypress-real-events/support'
import './execute-spec'
beforeEach(() => {
// this is always 0, since we only destroy the AUT when using
// `experimentalSingleTabRunMode, which is not a valid experiment for for e2e testing.
// @ts-ignore - dynamically defined during tests using
expect(window.top?.getEventManager().autDestroyedCount).to.be.undefined
})

View File

@@ -35,6 +35,14 @@ export class AutIframe {
return $iframe
}
destroy () {
if (!this.$iframe) {
throw Error(`Cannot call #remove without first calling #create`)
}
this.$iframe.remove()
}
showInitialBlankContents () {
this._showContents(blankContents.initial())
}

View File

@@ -11,6 +11,7 @@ import type { Socket } from '@packages/socket/lib/browser'
import * as cors from '@packages/network/lib/cors'
import { automation, useRunnerUiStore } from '../store'
import { useScreenshotStore } from '../store/screenshot-store'
import { getAutIframeModel } from '.'
export type CypressInCypressMochaEvent = Array<Array<string | Record<string, any>>>
@@ -54,6 +55,8 @@ export class EventManager {
studioRecorder: any
selectorPlaygroundModel: any
cypressInCypressMochaEvents: CypressInCypressMochaEvent[] = []
// Used for testing the experimentalSingleTabRunMode experiment. Ensures AUT is correctly destroyed between specs.
ws: Socket
constructor (
// import '@packages/driver'
@@ -64,10 +67,11 @@ export class EventManager {
selectorPlaygroundModel: any,
// StudioRecorder constructor
StudioRecorderCtor: any,
private ws: Socket,
ws: Socket,
) {
this.studioRecorder = new StudioRecorderCtor(this)
this.selectorPlaygroundModel = selectorPlaygroundModel
this.ws = ws
}
getCypress () {
@@ -337,6 +341,13 @@ export class EventManager {
rerun()
})
this.ws.on('aut:destroy:init', () => {
const autIframe = getAutIframeModel()
autIframe.destroy()
this.ws.emit('aut:destroy:complete')
})
// @ts-ignore
const $window = this.$CypressDriver.$(window)

View File

@@ -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",
@@ -120,6 +121,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",
@@ -197,6 +199,7 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
"experimentalSessionAndOrigin",
"experimentalModifyObstructiveThirdPartyCode",
"experimentalSourceRewriting",
"experimentalSingleTabRunMode",
"fileServerFolder",
"fixturesFolder",
"excludeSpecPattern",

View File

@@ -75,7 +75,7 @@ export function resetIssuedWarnings () {
issuedWarnings.clear()
}
const validateNoBreakingOptions = (breakingCfgOptions: BreakingOption[], cfg: any, onWarning: ErrorHandler, onErr: ErrorHandler, testingType?: TestingType) => {
const validateNoBreakingOptions = (breakingCfgOptions: Readonly<BreakingOption[]>, cfg: any, onWarning: ErrorHandler, onErr: ErrorHandler, testingType?: TestingType) => {
breakingCfgOptions.forEach(({ name, errorKey, newName, isWarning, value }) => {
if (_.has(cfg, name)) {
if (value && cfg[name] !== value) {

View File

@@ -3,31 +3,37 @@ import path from 'path'
// @ts-ignore
import pkg from '@packages/root'
import type { AllCypressErrorNames } from '@packages/errors'
import type { TestingType } from '@packages/types'
import * as validate from './validation'
export type BreakingOptionErrorKey =
| 'COMPONENT_FOLDER_REMOVED'
| 'INTEGRATION_FOLDER_REMOVED'
| 'CONFIG_FILE_INVALID_ROOT_CONFIG'
| 'CONFIG_FILE_INVALID_ROOT_CONFIG_E2E'
| 'CONFIG_FILE_INVALID_ROOT_CONFIG_COMPONENT'
| 'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_COMPONENT'
| 'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_E2E'
| 'EXPERIMENTAL_COMPONENT_TESTING_REMOVED'
| 'EXPERIMENTAL_SAMESITE_REMOVED'
| 'EXPERIMENTAL_NETWORK_STUBBING_REMOVED'
| 'EXPERIMENTAL_RUN_EVENTS_REMOVED'
| 'EXPERIMENTAL_SESSION_SUPPORT_REMOVED'
| 'EXPERIMENTAL_SHADOW_DOM_REMOVED'
| 'EXPERIMENTAL_STUDIO_REMOVED'
| 'FIREFOX_GC_INTERVAL_REMOVED'
| 'NODE_VERSION_DEPRECATION_SYSTEM'
| 'NODE_VERSION_DEPRECATION_BUNDLED'
| 'PLUGINS_FILE_CONFIG_OPTION_REMOVED'
| 'RENAMED_CONFIG_OPTION'
| 'TEST_FILES_RENAMED'
const BREAKING_OPTION_ERROR_KEY: Readonly<AllCypressErrorNames[]> = [
'COMPONENT_FOLDER_REMOVED',
'INTEGRATION_FOLDER_REMOVED',
'CONFIG_FILE_INVALID_ROOT_CONFIG',
'CONFIG_FILE_INVALID_ROOT_CONFIG_E2E',
'CONFIG_FILE_INVALID_ROOT_CONFIG_COMPONENT',
'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_COMPONENT',
'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_E2E',
'EXPERIMENTAL_COMPONENT_TESTING_REMOVED',
'EXPERIMENTAL_SAMESITE_REMOVED',
'EXPERIMENTAL_NETWORK_STUBBING_REMOVED',
'EXPERIMENTAL_RUN_EVENTS_REMOVED',
'EXPERIMENTAL_SESSION_SUPPORT_REMOVED',
'EXPERIMENTAL_SINGLE_TAB_RUN_MODE',
'EXPERIMENTAL_SHADOW_DOM_REMOVED',
'EXPERIMENTAL_STUDIO_REMOVED',
'EXPERIMENTAL_STUDIO_REMOVED',
'FIREFOX_GC_INTERVAL_REMOVED',
'NODE_VERSION_DEPRECATION_SYSTEM',
'NODE_VERSION_DEPRECATION_BUNDLED',
'PLUGINS_FILE_CONFIG_OPTION_REMOVED',
'RENAMED_CONFIG_OPTION',
'TEST_FILES_RENAMED',
] as const
export type BreakingOptionErrorKey = typeof BREAKING_OPTION_ERROR_KEY[number]
export type OverrideLevel = 'any' | 'suite' | 'never'
@@ -211,6 +217,12 @@ const driverConfigOptions: Array<DriverConfigOption> = [
validation: validate.isBoolean,
isExperimental: true,
requireRestartOnChange: 'server',
}, {
name: 'experimentalSingleTabRunMode',
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
requireRestartOnChange: 'server',
}, {
name: 'fileServerFolder',
defaultValue: '',
@@ -520,7 +532,7 @@ export const options: Array<DriverConfigOption | RuntimeConfigOption> = [
/**
* Values not allowed in 10.X+ in the root, e2e and component config
*/
export const breakingOptions: Array<BreakingOption> = [
export const breakingOptions: Readonly<BreakingOption[]> = [
{
name: 'blacklistHosts',
errorKey: 'RENAMED_CONFIG_OPTION',
@@ -592,7 +604,7 @@ export const breakingOptions: Array<BreakingOption> = [
newName: 'specPattern',
isWarning: false,
},
]
] as const
export const breakingRootOptions: Array<BreakingOption> = [
{
@@ -645,6 +657,12 @@ export const breakingRootOptions: Array<BreakingOption> = [
export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component: Array<BreakingOption> } = {
e2e: [
{
name: 'experimentalSingleTabRunMode',
errorKey: 'EXPERIMENTAL_SINGLE_TAB_RUN_MODE',
isWarning: false,
testingTypes: ['e2e'],
},
{
name: 'indexHtmlFile',
errorKey: 'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_E2E',

View File

@@ -36,7 +36,7 @@
</head>
<body><pre><span style="color:#e05561">We detected that you have versions of dependencies that are not officially supported:<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#4f5666"> - <span style="color:#e05561"><span style="color:#4ec4ff">`vite`. Expected &gt;=2.0.0, found 1.0.0.<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#4f5666"> - <span style="color:#e05561"><span style="color:#4ec4ff">`vite`. Expected ^=2.0.0 || ^=3.0.0, found 1.0.0.<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">If you&#39;re experiencing problems, downgrade dependencies and restart Cypress.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap" rel="stylesheet">
<style>
body {
font-family: "Courier Prime", Courier, monospace;
padding: 0 1em;
line-height: 1.4;
color: #eee;
background-color: #111;
}
pre {
padding: 0 0;
margin: 0 0;
font-family: "Courier Prime", Courier, monospace;
}
body {
margin: 5px;
padding: 0;
overflow: hidden;
}
pre {
white-space: pre-wrap;
word-break: break-word;
-webkit-font-smoothing: antialiased;
}
</style>
</head>
<body><pre><span style="color:#e05561">The <span style="color:#e5e510">experimentalSingleTabRunMode<span style="color:#e05561"> experiment is currently only supported for Component Testing.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">If you have feedback about the experiment, please join the discussion here: http://on.cypress.io/single-tab-run-mode<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -1085,6 +1085,12 @@ export const AllCypressErrors = {
You can safely remove the ${fmt.highlight(`experimentalStudio`)} configuration option from your config.`
},
EXPERIMENTAL_SINGLE_TAB_RUN_MODE: () => {
return errTemplate`\
The ${fmt.highlight(`experimentalSingleTabRunMode`)} experiment is currently only supported for Component Testing.
If you have feedback about the experiment, please join the discussion here: http://on.cypress.io/single-tab-run-mode`
},
FIREFOX_GC_INTERVAL_REMOVED: () => {
return errTemplate`\
The ${fmt.highlight(`firefoxGcInterval`)} configuration option was removed in ${fmt.cypressVersion(`8.0.0`)}. It was introduced to work around a bug in Firefox 79 and below.

View File

@@ -1184,7 +1184,7 @@ describe('visual error templates', () => {
package: 'vite',
installer: 'vite',
description: 'Vite is dev server that serves your source files over native ES modules',
minVersion: '>=2.0.0',
minVersion: '^=2.0.0 || ^=3.0.0',
},
satisfied: false,
detectedVersion: '1.0.0',
@@ -1194,5 +1194,11 @@ describe('visual error templates', () => {
],
}
},
EXPERIMENTAL_SINGLE_TAB_RUN_MODE: () => {
return {
default: [],
}
},
})
})

View File

@@ -4,7 +4,7 @@ import { BaseErrorFragmentDoc } from '../../../../launchpad/src/generated/graphq
import dedent from 'dedent'
// Selectors
const headerSelector = 'h1[data-testid=error-header]'
const headerSelector = 'h1[data-cy=error-header]'
const messageSelector = '[data-testid=error-message]'
const retryButtonSelector = 'button[data-testid=error-retry-button]'
const docsButtonSelector = 'a[data-testid=error-docs-button]'

View File

@@ -8,7 +8,7 @@
<h1
v-if="baseError.title"
class="font-medium leading-snug text-32px text-gray-900"
data-testid="error-header"
data-cy="error-header"
>
<slot name="header">
{{ baseError.title }}

View File

@@ -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',

View File

@@ -67,6 +67,26 @@ describe('baseUrl', () => {
})
})
describe('experimentalSingleTabRunMode', () => {
it('is a valid config for component testing', () => {
cy.scaffoldProject('experimentalSingleTabRunMode')
cy.openProject('experimentalSingleTabRunMode')
cy.visitLaunchpad()
cy.get('[data-cy-testingtype="component"]').click()
cy.get('h1').contains('Initializing Config').should('not.exist')
cy.get('h1').contains('Choose a Browser')
})
it('is not a valid config for e2e testing', () => {
cy.scaffoldProject('experimentalSingleTabRunMode')
cy.openProject('experimentalSingleTabRunMode')
cy.visitLaunchpad()
cy.get('[data-cy-testingtype="e2e"]').click()
cy.findByTestId('error-header').contains('Cypress configuration error')
cy.findByTestId('alert-body').contains('The experimentalSingleTabRunMode experiment is currently only supported for Component Testing.')
})
})
describe('experimentalStudio', () => {
it('should show experimentalStudio warning if Cypress detects experimentalStudio config has been set', () => {
cy.scaffoldProject('experimental-studio')

View File

@@ -56,6 +56,7 @@ const _summaries: StringValues = {
experimentalSessionAndOrigin: 'Enables cross-origin and improved session support, including the `cy.origin` and `cy.session` commands.',
experimentalModifyObstructiveThirdPartyCode: 'Applies `modifyObstructiveCode` to third party `.html` and `.js`, removes subresource integrity, and modifies the user agent in Electron.',
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 spec isolation for faster run mode execution.',
experimentalStudio: 'Generate and save commands directly to your test suite by interacting with your app as an end user would.',
}
@@ -74,6 +75,7 @@ const _names: StringValues = {
experimentalInteractiveRunEvents: 'Interactive Mode Run Events',
experimentalSessionAndOrigin: 'Cross-origin and Session',
experimentalModifyObstructiveThirdPartyCode: 'Modify Obstructive Third Party Code',
experimentalSingleTabRunMode: 'Single Tab Run Mode',
experimentalSourceRewriting: 'Improved Source Rewriting',
experimentalStudio: 'Studio',
}

View File

@@ -776,6 +776,12 @@ module.exports = {
displayRunStarting,
navigateToNextSpec (spec) {
debug('navigating to next spec %s', spec)
return openProject.changeUrlToSpec(spec)
},
exitEarly (err) {
debug('set early exit error: %s', err.stack)
@@ -1105,6 +1111,16 @@ module.exports = {
return this.currentSetScreenshotMetadata(data)
}
if (options.experimentalSingleTabRunMode && options.testingType === 'component' && !options.isFirstSpec) {
// reset browser state to match default behavior when opening/closing a new tab
return openProject.resetBrowserState().then(() => {
// 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...')
@@ -1168,7 +1184,7 @@ module.exports = {
},
waitForTestsToFinishRunning (options = {}) {
const { project, screenshots, startedVideoCapture, endVideoCapture, videoName, compressedVideoName, videoCompression, videoUploadOnPasses, exit, spec, estimated, quiet, config, shouldKeepTabOpen } = options
const { project, screenshots, startedVideoCapture, endVideoCapture, videoName, compressedVideoName, videoCompression, videoUploadOnPasses, exit, spec, estimated, quiet, config, shouldKeepTabOpen, testingType } = options
// https://github.com/cypress-io/cypress/issues/2370
// delay 1 second if we're recording a video to give
@@ -1244,17 +1260,22 @@ 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)
// }
const usingExperimentalSingleTabMode = testingType === 'component' && config.experimentalSingleTabRunMode
debug('resetting server state')
openProject.projectBase.server.reset()
if (usingExperimentalSingleTabMode) {
await openProject.projectBase.server.destroyAut()
}
// we do not support experimentalSingleTabRunMode for e2e
if (!usingExperimentalSingleTabMode) {
debug('attempting to close the browser tab')
await openProject.resetBrowserTabsForNextTest(shouldKeepTabOpen)
debug('resetting server state')
openProject.projectBase.server.reset()
}
if (videoExists && !skippedSpec && endVideoCapture && !videoCaptureFailed) {
const ffmpegChaptersConfig = videoCapture.generateFfmpegChaptersConfig(results.tests)
@@ -1479,6 +1500,7 @@ module.exports = {
endVideoCapture: videoRecordProps.endVideoCapture,
startedVideoCapture: videoRecordProps.startedVideoCapture,
exit: options.exit,
testingType: options.testingType,
videoCompression: options.videoCompression,
videoUploadOnPasses: options.videoUploadOnPasses,
quiet: options.quiet,
@@ -1496,8 +1518,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
}),
})
})

View File

@@ -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

View File

@@ -48,4 +48,8 @@ export class ServerCt extends ServerBase<SocketCt> {
})
})
}
destroyAut () {
return this.socket.destroyAut()
}
}

View File

@@ -609,4 +609,8 @@ export class SocketBase {
close () {
return this._io?.close()
}
changeToUrl (url: string) {
return this.toRunner('change:to:url', url)
}
}

View File

@@ -1,12 +1,16 @@
import Debug from 'debug'
import type * as socketIo from '@packages/socket'
import devServer from '@packages/server/lib/plugins/dev-server'
import { SocketBase } from '@packages/server/lib/socket-base'
import dfd from 'p-defer'
import type { Socket } from '@packages/socket'
import type { DestroyableHttpServer } from '@packages/server/lib/util/server_destroy'
import assert from 'assert'
const debug = Debug('cypress:server:socket-ct')
export class SocketCt extends SocketBase {
#destroyAutPromise?: dfd.DeferredPromise<void>
constructor (config: Record<string, any>) {
super(config)
@@ -20,9 +24,22 @@ export class SocketCt extends SocketBase {
startListening (server: DestroyableHttpServer, automation, config, options) {
return super.startListening(server, automation, config, options, {
onSocketConnection (socket: socketIo.SocketIOServer) {
onSocketConnection: (socket: Socket) => {
debug('do onSocketConnection')
socket.on('aut:destroy:complete', () => {
assert(this.#destroyAutPromise)
this.#destroyAutPromise.resolve()
})
},
})
}
destroyAut () {
this.#destroyAutPromise = dfd()
this.toRunner('aut:destroy:init')
return this.#destroyAutPromise.promise
}
}

View File

@@ -1488,6 +1488,7 @@ describe('lib/config', () => {
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalSessionAndOrigin: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
fileServerFolder: { value: '', from: 'default' },
@@ -1577,6 +1578,7 @@ describe('lib/config', () => {
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalSessionAndOrigin: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
env: {

View File

@@ -531,3 +531,210 @@ exports['React major versions with Vite executes all of the tests for React v18
`
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.
If you're experiencing problems, downgrade dependencies and restart Cypress.
30 modules
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
│ Specs: 4 found (1_fails.cy.js, 2_foo.cy.js, 3_retries.cy.js, 999_final.cy.js) │
│ Searched: **/*.cy.js │
│ Experiments: experimentalSingleTabRunMode=true │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: 1_fails.cy.js (1 of 4)
simple failing spec
1) fails
2) fails again
0 passing
2 failing
1) simple failing spec
fails:
AssertionError: expected 1 to equal 2
+ expected - actual
-1
+2
[stack trace lines]
2) simple failing 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: 1_fails.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- /XXX/XXX/XXX/cypress/screenshots/1_fails.cy.js/simple failing spec -- fails (fai (1280x720)
led).png
- /XXX/XXX/XXX/cypress/screenshots/1_fails.cy.js/simple failing spec -- fails agai (1280x720)
n (failed).png
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/1_fails.cy.js.mp4 (X second)
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: 2_foo.cy.js (2 of 4)
component
✓ passes
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: 2_foo.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/2_foo.cy.js.mp4 (X second)
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: 3_retries.cy.js (3 of 4)
retries
(Attempt 1 of 2) passes after 1 failure
✓ passes after 1 failure
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: 3_retries.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/3_retries.cy.js.mp4 (X second)
────────────────────────────────────────────────────────────────────────────────────────────────────
Running: 999_final.cy.js (4 of 4)
✓ verifies AUT is destroyed after each spec
1 passing
(Results)
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Tests: 1 │
│ Passing: 1 │
│ Failing: 0 │
│ Pending: 0 │
│ Skipped: 0 │
│ Screenshots: 0 │
│ Video: true │
│ Duration: X seconds │
│ Spec Ran: 999_final.cy.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/999_final.cy.js.mp4 (X second)
====================================================================================================
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ✖ 1_fails.cy.js XX:XX 2 - 2 - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ 2_foo.cy.js XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ 3_retries.cy.js XX:XX 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ✔ 999_final.cy.js XX:XX 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
✖ 1 of 4 failed (25%) XX:XX 5 3 2 - -
`

View File

@@ -1,4 +1,4 @@
describe('simple passing spec', () => {
describe('simple failing spec', () => {
it('fails', () => {
expect(1).to.eq(2)
})

View File

@@ -0,0 +1,15 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents: (on, config) => config,
// invalid - used for e2e testing to verify error is shown
experimentalSingleTabRunMode: true,
},
component: {
experimentalSingleTabRunMode: true,
devServer: {
bundler: 'webpack',
},
},
})

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@@ -0,0 +1,11 @@
before(() => {
const eventManager = window.top.getEventManager()
eventManager.ws.once('aut:destroy:init', () => {
if (!eventManager.autDestroyedCount) {
eventManager.autDestroyedCount = 0
}
eventManager.autDestroyedCount += 1
})
})

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

View File

@@ -0,0 +1,9 @@
describe('simple failing spec', () => {
it('fails', () => {
expect(1).to.eq(2)
})
it('fails again', () => {
expect(1).to.eq(3)
})
})

View File

@@ -0,0 +1,5 @@
describe('component', () => {
it('passes', () => {
expect('foo').to.eq('foo')
})
})

View File

@@ -0,0 +1,12 @@
describe('retries', () => {
let i = 0
it('passes after 1 failure', { retries: 1 }, () => {
if (i === 0) {
i++
expect(1).to.eq(2)
}
expect(1).to.eq(1)
})
})

View File

@@ -0,0 +1,5 @@
it('verifies AUT is destroyed after each spec', () => {
// there are three other specs that have run by now
// so the aut destroy count should be 3
expect(window.top.getEventManager().autDestroyedCount).to.eq(3)
})

View File

@@ -0,0 +1 @@
module.exports = {}

View File

@@ -124,3 +124,16 @@ describe(`Angular CLI major versions`, () => {
})
}
})
describe('experimentalSingleTabRunMode', function () {
systemTests.setup()
systemTests.it('executes all specs in a single tab', {
project: 'experimentalSingleTabRunMode',
testingType: 'component',
spec: '**/*.cy.js',
browser: 'chrome',
snapshot: true,
expectedExitCode: 2,
})
})