diff --git a/.circleci/cache-version.txt b/.circleci/cache-version.txt index 1cd9239e8c..c947f7084f 100644 --- a/.circleci/cache-version.txt +++ b/.circleci/cache-version.txt @@ -1,3 +1,3 @@ # Bump this version to force CI to re-create the cache from scratch. -08-10-23 \ No newline at end of file +08-10-23 diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 9b391b874c..f803aa44e2 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -31,7 +31,7 @@ mainBuildFilters: &mainBuildFilters # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - 'publish-binary' - - 'chore/merge_develop_into_release_13' + - 'ryanm/feat/remove-protocol-environment-variable-from-binary' # 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 @@ -42,7 +42,7 @@ macWorkflowFilters: &darwin-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'chore/merge_develop_into_release_13', << pipeline.git.branch >> ] + - equal: [ 'ryanm/feat/remove-protocol-environment-variable-from-binary', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -53,6 +53,11 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] + - equal: [ 'publish-binary', << pipeline.git.branch >> ] + - equal: [ 'ryanm/feat/remove-protocol-environment-variable-from-binary', << pipeline.git.branch >> ] + - matches: + pattern: /^release\/\d+\.\d+\.\d+$/ + value: << pipeline.git.branch >> # uncomment & add to the branch conditions below to disable the main linux # flow if we don't want to test it for a certain branch @@ -69,7 +74,7 @@ windowsWorkflowFilters: &windows-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'chore/merge_develop_into_release_13', << pipeline.git.branch >> ] + - equal: [ 'ryanm/feat/remove-protocol-environment-variable-from-binary', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -139,7 +144,7 @@ commands: name: Set environment variable to determine whether or not to persist artifacts command: | echo "Setting SHOULD_PERSIST_ARTIFACTS variable" - echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "publish-binary" && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "chore/merge_develop_into_release_13" ]]; then + echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "publish-binary" && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "ryanm/feat/remove-protocol-environment-variable-from-binary" ]]; then export SHOULD_PERSIST_ARTIFACTS=true fi' >> "$BASH_ENV" # You must run `setup_should_persist_artifacts` command and be using bash before running this command diff --git a/packages/app/cypress/e2e/reporter_header.cy.ts b/packages/app/cypress/e2e/reporter_header.cy.ts index ce42240a59..306b1940a5 100644 --- a/packages/app/cypress/e2e/reporter_header.cy.ts +++ b/packages/app/cypress/e2e/reporter_header.cy.ts @@ -13,7 +13,7 @@ describe('Reporter Header', () => { cy.get('body').type('f') cy.get('[data-selected-spec="true"]').should('contain', 'dom-content').should('have.length', '1') - cy.get('[data-selected-spec="false"]').should('have.length', '29') + cy.get('[data-selected-spec="false"]').should('have.length', '30') }) // TODO: Reenable as part of https://github.com/cypress-io/cypress/issues/23902 diff --git a/packages/app/cypress/e2e/specs_list_e2e.cy.ts b/packages/app/cypress/e2e/specs_list_e2e.cy.ts index c00a56f721..06a1ab3312 100644 --- a/packages/app/cypress/e2e/specs_list_e2e.cy.ts +++ b/packages/app/cypress/e2e/specs_list_e2e.cy.ts @@ -152,7 +152,7 @@ describe('App: Spec List (E2E)', () => { it('displays only matching spec', function () { cy.get('button') - .contains('25 matches') + .contains('26 matches') .should('not.contain.text', 'of') clearSearchAndType('content') @@ -160,13 +160,13 @@ describe('App: Spec List (E2E)', () => { .should('have.length', 3) .and('contain', 'dom-content.spec.js') - cy.get('button').contains('3 of 25 matches') + cy.get('button').contains('3 of 26 matches') cy.findByLabelText('Search specs').clear().type('asdf') cy.findAllByTestId('spec-item') .should('have.length', 0) - cy.get('button').contains('0 of 25 matches') + cy.get('button').contains('0 of 26 matches') }) it('only shows matching folders', () => { @@ -217,7 +217,7 @@ describe('App: Spec List (E2E)', () => { cy.findByLabelText('Search specs') .should('have.value', '') - cy.get('button').contains('25 matches') + cy.get('button').contains('26 matches') }) it('clears the filter if the user presses ESC key', function () { @@ -226,7 +226,7 @@ describe('App: Spec List (E2E)', () => { cy.get('@searchField').should('have.value', '') - cy.get('button').contains('25 matches') + cy.get('button').contains('26 matches') }) it('shows empty message if no results', function () { @@ -242,7 +242,7 @@ describe('App: Spec List (E2E)', () => { cy.findByText('Clear search').click() cy.focused().should('have.id', 'spec-filter') - cy.get('button').contains('25 matches') + cy.get('button').contains('26 matches') }) it('normalizes directory path separators for Windows', function () { diff --git a/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts b/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts index b8496300ab..57def43dd5 100644 --- a/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts +++ b/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts @@ -76,6 +76,7 @@ describe('specChange subscription', () => { getPathForPlatform('cypress/e2e/admin_users/admin_users_list.spec.js'), getPathForPlatform('cypress/e2e/admin_users/admin.user/foo_list.spec.js'), getPathForPlatform('cypress/e2e/test-isolation.spec.js'), + getPathForPlatform('cypress/e2e/test-isolation-describe-config.spec.js'), getPathForPlatform('cypress/e2e/z001.spec.js'), getPathForPlatform('cypress/e2e/z002.spec.js'), getPathForPlatform('cypress/e2e/z003.spec.js'), @@ -120,6 +121,7 @@ describe('specChange subscription', () => { getPathForPlatform('cypress/e2e/admin_users/admin_users_list.spec.js'), getPathForPlatform('cypress/e2e/admin_users/admin.user/foo_list.spec.js'), getPathForPlatform('cypress/e2e/test-isolation.spec.js'), + getPathForPlatform('cypress/e2e/test-isolation-describe-config.spec.js'), getPathForPlatform('cypress/e2e/z001.spec.js'), getPathForPlatform('cypress/e2e/z002.spec.js'), getPathForPlatform('cypress/e2e/z003.spec.js'), @@ -190,7 +192,7 @@ e2e: { cy.get('body').type('f') cy.get('[data-cy="spec-file-item"]') - .should('have.length', 25) + .should('have.length', 26) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -201,7 +203,7 @@ e2e: { }, { path: getPathForPlatform('cypress/e2e/new-file.spec.js') }) cy.get('[data-cy="spec-file-item"]') - .should('have.length', 26) + .should('have.length', 27) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -216,7 +218,7 @@ e2e: { cy.get('body').type('f') cy.get('[data-cy="spec-file-item"]') - .should('have.length', 25) + .should('have.length', 26) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -227,7 +229,7 @@ e2e: { }, { path: getPathForPlatform('cypress/e2e/dom-list.spec.js') }) cy.get('[data-cy="spec-file-item"]') - .should('have.length', 24) + .should('have.length', 25) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -258,6 +260,7 @@ e2e: { getPathForPlatform('cypress/e2e/admin_users/admin_users_list.spec.js'), getPathForPlatform('cypress/e2e/admin_users/admin.user/foo_list.spec.js'), getPathForPlatform('cypress/e2e/test-isolation.spec.js'), + getPathForPlatform('cypress/e2e/test-isolation-describe-config.spec.js'), getPathForPlatform('cypress/e2e/z001.spec.js'), getPathForPlatform('cypress/e2e/z002.spec.js'), getPathForPlatform('cypress/e2e/z003.spec.js'), @@ -289,7 +292,7 @@ e2e: { cy.get('body').type('f') cy.get('[data-cy="spec-file-item"]') - .should('have.length', 25) + .should('have.length', 26) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -332,14 +335,14 @@ e2e: { cy.get('[data-cy="spec-pattern"]').contains('cypress/e2e/**/*.spec.{js,ts}') cy.get('[data-cy="file-match-indicator"]') - .should('contain', '25 matches') + .should('contain', '26 matches') cy.withCtx(async (ctx, o) => { await ctx.actions.file.writeFileInProject(o.path, '') }, { path: getPathForPlatform('cypress/e2e/new-file.spec.js') }) cy.get('[data-cy="file-match-indicator"]') - .should('contain', '26 matches') + .should('contain', '27 matches') }) it('responds to specChange event for a removed file', () => { @@ -349,14 +352,14 @@ e2e: { cy.get('[data-cy="spec-pattern"]').contains('cypress/e2e/**/*.spec.{js,ts}') cy.get('[data-cy="file-match-indicator"]') - .should('contain', '25 matches') + .should('contain', '26 matches') cy.withCtx(async (ctx, o) => { await ctx.actions.file.removeFileInProject(o.path) }, { path: getPathForPlatform('cypress/e2e/dom-list.spec.js') }) cy.get('[data-cy="file-match-indicator"]') - .should('contain', '24 matches') + .should('contain', '25 matches') }) it('handles removing the last file', () => { @@ -384,6 +387,7 @@ e2e: { getPathForPlatform('cypress/e2e/admin_users/admin_users_list.spec.js'), getPathForPlatform('cypress/e2e/admin_users/admin.user/foo_list.spec.js'), getPathForPlatform('cypress/e2e/test-isolation.spec.js'), + getPathForPlatform('cypress/e2e/test-isolation-describe-config.spec.js'), getPathForPlatform('cypress/e2e/z001.spec.js'), getPathForPlatform('cypress/e2e/z002.spec.js'), getPathForPlatform('cypress/e2e/z003.spec.js'), @@ -414,7 +418,7 @@ e2e: { cy.get('[data-cy="spec-pattern"]').contains('cypress/e2e/**/*.spec.{js,ts}') cy.get('[data-cy="file-match-indicator"]') - .should('contain', '25 matches') + .should('contain', '26 matches') cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject('cypress.config.js', diff --git a/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js b/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js index b5f14da191..6f18e293b1 100644 --- a/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js +++ b/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js @@ -59,7 +59,7 @@ describe('cy.session', { retries: 0 }, () => { .then(async () => { cy.spy(Cypress, 'action').log(false) - await Cypress.action('runner:test:before:after:run:async', {}, Cypress.state('runnable')) + await Cypress.action('runner:test:before:after:run:async', {}, Cypress.state('runnable'), { nextTestHasTestIsolationOn: true }) expect(Cypress.action).to.be.calledWith('cy:url:changed', '') expect(Cypress.action).to.be.calledWith('cy:visit:blank', { testIsolation: true }) @@ -68,7 +68,21 @@ describe('cy.session', { retries: 0 }, () => { .should('eq', 'about:blank') }) - it('clears the browser cookie before each run', () => { + it('does not clear the page before the end of each run if the next test has test isolation off', () => { + cy.visit('/fixtures/form.html') + .then(async () => { + cy.spy(Cypress, 'action').log(false) + + await Cypress.action('runner:test:before:after:run:async', {}, Cypress.state('runnable'), { nextTestHasTestIsolationOn: false }) + + expect(Cypress.action).not.to.be.calledWith('cy:url:changed', '') + expect(Cypress.action).not.to.be.calledWith('cy:visit:blank', { testIsolation: true }) + }) + .url() + .should('not.eq', 'about:blank') + }) + + it('clears the browser cookie after each run', () => { cy.window() .then((win) => { win.cookie = 'key=value; SameSite=Strict; Secure; Path=/fixtures' @@ -76,11 +90,25 @@ describe('cy.session', { retries: 0 }, () => { .then(async () => { cy.spy(Cypress, 'action').log(false) - await Cypress.action('runner:test:before:after:run:async', {}, Cypress.state('runnable')) + await Cypress.action('runner:test:before:after:run:async', {}, Cypress.state('runnable'), { nextTestHasTestIsolationOn: true }) }) cy.window().its('cookie').should('be.undefined') }) + + it('does not clear the browser cookie after each run if the next test has test isolation off', () => { + cy.window() + .then((win) => { + win.cookie = 'key=value; SameSite=Strict; Secure; Path=/fixtures' + }) + .then(async () => { + cy.spy(Cypress, 'action').log(false) + + await Cypress.action('runner:test:before:after:run:async', {}, Cypress.state('runnable'), { nextTestHasTestIsolationOn: false }) + }) + + cy.window().its('cookie').should('eq', 'key=value; SameSite=Strict; Secure; Path=/fixtures') + }) }) describe('test:before:run:async', () => { diff --git a/packages/driver/src/cy/commands/sessions/index.ts b/packages/driver/src/cy/commands/sessions/index.ts index 780558bf81..a484791ee5 100644 --- a/packages/driver/src/cy/commands/sessions/index.ts +++ b/packages/driver/src/cy/commands/sessions/index.ts @@ -32,12 +32,12 @@ export default function (Commands, Cypress, cy) { } }) - Cypress.on('test:before:after:run:async', () => { - if (!Cypress.config('testIsolation')) { - return + Cypress.on('test:before:after:run:async', (test, Cypress, { nextTestHasTestIsolationOn }: {nextTestHasTestIsolationOn?: boolean} = {}) => { + if (nextTestHasTestIsolationOn) { + return navigateAboutBlank({ inBetweenTestsAndNextTestHasTestIsolationOn: true }) } - return navigateAboutBlank() + return }) Cypress.on('test:before:run:async', async () => { diff --git a/packages/driver/src/cy/commands/sessions/utils.ts b/packages/driver/src/cy/commands/sessions/utils.ts index dee035138a..0fea1d548c 100644 --- a/packages/driver/src/cy/commands/sessions/utils.ts +++ b/packages/driver/src/cy/commands/sessions/utils.ts @@ -191,9 +191,11 @@ const getPostMessageLocalStorage = (specWindow, origins): Promise => { }) } -function navigateAboutBlank () { - // Component testing does not support navigation and handles clearing the page via mount utils - if (Cypress.testingType === 'component' || !Cypress.config('testIsolation')) { +function navigateAboutBlank ({ inBetweenTestsAndNextTestHasTestIsolationOn }: { inBetweenTestsAndNextTestHasTestIsolationOn?: boolean } = {}) { + // Component testing never supports navigating to about:blank as that is handled by its unmount mechanism + // When test isolation is off we typically don't navigate to about blank; however if we are in between tests and the next + // test has test isolation on, we need to navigate to about blank to ensure the next test is not affected by the previous test + if (Cypress.testingType === 'component' || (!Cypress.config('testIsolation') && !inBetweenTestsAndNextTestHasTestIsolationOn)) { return Promise.resolve() } diff --git a/packages/driver/src/cypress/runner.ts b/packages/driver/src/cypress/runner.ts index 7c357c6910..1b70f8bf31 100644 --- a/packages/driver/src/cypress/runner.ts +++ b/packages/driver/src/cypress/runner.ts @@ -46,7 +46,7 @@ const duration = (before: Date, after: Date) => { return Number(before) - Number(after) } -const fire = (event: typeof RUNNER_EVENTS[number], runnable, Cypress) => { +const fire = (event: typeof RUNNER_EVENTS[number], runnable, Cypress, ...args) => { debug('fire: %o', { event }) if (runnable._fired == null) { runnable._fired = {} @@ -59,7 +59,7 @@ const fire = (event: typeof RUNNER_EVENTS[number], runnable, Cypress) => { return } - return Cypress.action(event, wrap(runnable), runnable) + return Cypress.action(event, wrap(runnable), runnable, ...args) } const fired = (event: typeof RUNNER_EVENTS[number], runnable) => { @@ -74,10 +74,10 @@ const testBeforeRunAsync = (test, Cypress) => { }) } -const testBeforeAfterRunAsync = (test, Cypress) => { +const testBeforeAfterRunAsync = (test, Cypress, ...args) => { return Promise.try(() => { if (!fired(TEST_BEFORE_AFTER_RUN_ASYNC_EVENT, test)) { - return fire(TEST_BEFORE_AFTER_RUN_ASYNC_EVENT, test, Cypress) + return fire(TEST_BEFORE_AFTER_RUN_ASYNC_EVENT, test, Cypress, ...args) } }) } @@ -369,6 +369,16 @@ const lastTestThatWillRunInSuite = (test, tests) => { return isLastTest(test, tests) || (test.failedFromHookId && (test.hookName === 'before all')) } +const nextTestThatWillRunInSuite = (test, tests) => { + if (test.failedFromHookId && (test.hookName === 'before all')) { + return null + } + + const index = _.findIndex(tests, { id: test.id }) + + return index < tests.length - 1 ? tests[index + 1] : null +} + const isLastTest = (test, tests) => { return test.id === _.get(_.last(tests), 'id') } @@ -497,6 +507,11 @@ const overrideRunnerHook = (Cypress, _runner, getTestById, getTest, setTest, get // If we're not in open mode or we're in open mode and not the last test we reset state. // The last test will needs to stay so that the user can see what the end result of the AUT was. if (shouldAlwaysResetPage || !lastTestThatWillRunInSuite(test, getAllSiblingTests(topSuite, getTestById))) { + const nextTest = nextTestThatWillRunInSuite(test, getAllSiblingTests(topSuite, getTestById)) + const nextTestIsolationOverride = nextTest?._testConfig.unverifiedTestConfig.testIsolation + const topLevelTestIsolation = Cypress.originalConfig['testIsolation'] + const nextTestHasTestIsolationOn = nextTestIsolationOverride || (nextTestIsolationOverride === undefined && topLevelTestIsolation) + cy.state('duringUserTestExecution', false) Cypress.primaryOriginCommunicator.toAllSpecBridges('sync:state', { 'duringUserTestExecution': false }) // Remove window:load and window:before:load listeners so that navigating to about:blank doesn't fire in user code. @@ -505,7 +520,7 @@ const overrideRunnerHook = (Cypress, _runner, getTestById, getTest, setTest, get cy.removeAllListeners('window:load') // This will navigate to about:blank if test isolation is on - await testBeforeAfterRunAsync(test, Cypress) + await testBeforeAfterRunAsync(test, Cypress, { nextTestHasTestIsolationOn }) } testAfterRun(test, Cypress) diff --git a/packages/server/lib/cloud/api.ts b/packages/server/lib/cloud/api.ts index 27f046b9e6..893e01bfdb 100644 --- a/packages/server/lib/cloud/api.ts +++ b/packages/server/lib/cloud/api.ts @@ -633,7 +633,6 @@ module.exports = { }, getCaptureProtocolScript (url: string) { - // TODO(protocol): Ensure this is removed in production if (process.env.CYPRESS_LOCAL_PROTOCOL_PATH) { debugProtocol(`Loading protocol via script at local path %s`, process.env.CYPRESS_LOCAL_PROTOCOL_PATH) diff --git a/scripts/after-pack-hook.js b/scripts/after-pack-hook.js index cb1c776456..614bda5e30 100644 --- a/scripts/after-pack-hook.js +++ b/scripts/after-pack-hook.js @@ -6,7 +6,7 @@ const path = require('path') const { setupV8Snapshots } = require('@tooling/v8-snapshot') const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses') const { buildEntryPointAndCleanup } = require('./binary/binary-cleanup') -const { getIntegrityCheckSource, getBinaryEntryPointSource, getEncryptionFileSource, getCloudApiFileSource, validateEncryptionFile } = require('./binary/binary-sources') +const { getIntegrityCheckSource, getBinaryEntryPointSource, getEncryptionFileSource, getCloudEnvironmentFileSource, validateEncryptionFile, getProtocolFileSource, validateCloudEnvironmentFile, validateProtocolFile } = require('./binary/binary-sources') const CY_ROOT_DIR = path.join(__dirname, '..') @@ -61,18 +61,26 @@ module.exports = async function (params) { const binaryEntryPointSource = await getBinaryEntryPointSource() const encryptionFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/encryption.ts') const encryptionFileSource = await getEncryptionFileSource(encryptionFilePath) - const cloudApiFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/environment.ts') - const cloudApiFileSource = await getCloudApiFileSource(cloudApiFilePath) + const cloudEnvironmentFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/environment.ts') + const cloudEnvironmentFileSource = await getCloudEnvironmentFileSource(cloudEnvironmentFilePath) + const cloudApiFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/api.ts') + const cloudApiFileSource = await getProtocolFileSource(cloudApiFilePath) + const cloudProtocolFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/protocol.ts') + const cloudProtocolFileSource = await getProtocolFileSource(cloudProtocolFilePath) await Promise.all([ fs.writeFile(encryptionFilePath, encryptionFileSource), + fs.writeFile(cloudEnvironmentFilePath, cloudEnvironmentFileSource), fs.writeFile(cloudApiFilePath, cloudApiFileSource), + fs.writeFile(cloudProtocolFilePath, cloudProtocolFileSource), fs.writeFile(path.join(outputFolder, 'index.js'), binaryEntryPointSource), ]) await Promise.all([ validateEncryptionFile(encryptionFilePath), - validateEncryptionFile(cloudApiFilePath), + validateCloudEnvironmentFile(cloudEnvironmentFilePath), + validateProtocolFile(cloudApiFilePath), + validateProtocolFile(cloudProtocolFilePath), ]) await flipFuses( diff --git a/scripts/binary/binary-sources.js b/scripts/binary/binary-sources.js index dcf3ff1fd3..f334fa0a94 100644 --- a/scripts/binary/binary-sources.js +++ b/scripts/binary/binary-sources.js @@ -58,7 +58,7 @@ const validateEncryptionFile = async (encryptionFilePath) => { } } -const getCloudApiFileSource = async (cloudApiFilePath) => { +const getCloudEnvironmentFileSource = async (cloudApiFilePath) => { const fileContents = await fs.readFile(cloudApiFilePath, 'utf8') if (!fileContents.includes('process.env.CYPRESS_ENV_DEPENDENCIES')) { @@ -72,7 +72,7 @@ const getCloudApiFileSource = async (cloudApiFilePath) => { return fileContents } -const validateCloudApiFile = async (cloudApiFilePath) => { +const validateCloudEnvironmentFile = async (cloudApiFilePath) => { if (process.env.CYPRESS_ENV_DEPENDENCIES) { const afterReplaceCloudApi = await fs.readFile(cloudApiFilePath, 'utf8') @@ -82,11 +82,31 @@ const validateCloudApiFile = async (cloudApiFilePath) => { } } +const getProtocolFileSource = async (protocolFilePath) => { + const fileContents = await fs.readFile(protocolFilePath, 'utf8') + + if (!fileContents.includes('process.env.CYPRESS_LOCAL_PROTOCOL_PATH')) { + throw new Error(`Expected to find CYPRESS_LOCAL_PROTOCOL_PATH in protocol file`) + } + + return fileContents.replaceAll('process.env.CYPRESS_LOCAL_PROTOCOL_PATH', 'undefined') +} + +const validateProtocolFile = async (protocolFilePath) => { + const afterReplaceProtocol = await fs.readFile(protocolFilePath, 'utf8') + + if (afterReplaceProtocol.includes('process.env.CYPRESS_LOCAL_PROTOCOL_PATH')) { + throw new Error(`Expected process.env.CYPRESS_LOCAL_PROTOCOL_PATH to be stripped from protocol file`) + } +} + module.exports = { getBinaryEntryPointSource, getIntegrityCheckSource, getEncryptionFileSource, - getCloudApiFileSource, - validateCloudApiFile, validateEncryptionFile, + getCloudEnvironmentFileSource, + validateCloudEnvironmentFile, + getProtocolFileSource, + validateProtocolFile, } diff --git a/system-tests/projects/cypress-in-cypress/cypress/e2e/test-isolation-describe-config.spec.js b/system-tests/projects/cypress-in-cypress/cypress/e2e/test-isolation-describe-config.spec.js new file mode 100644 index 0000000000..da6916e844 --- /dev/null +++ b/system-tests/projects/cypress-in-cypress/cypress/e2e/test-isolation-describe-config.spec.js @@ -0,0 +1,132 @@ +const shouldAlwaysResetPage = (config) => { + const isRunMode = !config('isInteractive') + const isHeadedNoExit = config('browser').isHeaded && !config('exit') + + return isRunMode && !isHeadedNoExit +} + +const TEST_METADATA = { + 'passes 1': { + start: 'about:blank', + firesTestBeforeAfterRunAsync: true, + end: 'about:blank', + }, + 'passes 2': { + start: 'about:blank', + firesTestBeforeAfterRunAsync: true, + end: '/cypress/e2e/dom-content.html', + }, + 'passes 3': { + start: '/cypress/e2e/dom-content.html', + firesTestBeforeAfterRunAsync: true, + end: '/cypress/e2e/dom-content.html', + }, + 'passes 4': { + start: '/cypress/e2e/dom-content.html', + firesTestBeforeAfterRunAsync: true, + end: 'about:blank', + }, + 'passes 5': { + start: 'about:blank', + firesTestBeforeAfterRunAsync: true, + end: 'about:blank', + }, + 'passes 6': { + start: 'about:blank', + firesTestBeforeAfterRunAsync: true, + end: shouldAlwaysResetPage(Cypress.config) ? 'about:blank' : '/cypress/e2e/dom-content.html', + }, +} + +let cypressEventsHandled = 0 + +const testBeforeRun = (Cypress, ...args) => { + expect(Cypress.state('window').location.href.endsWith(TEST_METADATA[args[1].title].start)).to.equal(true) + cypressEventsHandled += 1 +} + +const testBeforeRunAsync = (Cypress, ...args) => { + expect(Cypress.state('window').location.href.endsWith(TEST_METADATA[args[1].title].start)).to.equal(true) + cypressEventsHandled += 1 +} + +const testBeforeAfterRunAsync = (Cypress, ...args) => { + expect(TEST_METADATA[args[1].title].firesTestBeforeAfterRunAsync).to.be.true + cypressEventsHandled += 1 +} + +const testAfterRun = (Cypress, ...args) => { + expect(Cypress.state('window').location.href.endsWith(TEST_METADATA[args[1].title].end)).to.equal(true) + cypressEventsHandled += 1 +} + +const testAfterRunAsync = (Cypress, ...args) => { + expect(Cypress.state('window').location.href.endsWith(TEST_METADATA[args[1].title].end)).to.equal(true) + cypressEventsHandled += 1 +} + +const cypressEvents = [ + ['test:before:run', testBeforeRun], + ['test:before:run:async', testBeforeRunAsync], + ['test:before:after:run:async', testBeforeAfterRunAsync], + ['test:after:run', testAfterRun], + ['test:after:run:async', testAfterRunAsync], +] + +cypressEvents.forEach(([event, handler]) => { + Cypress.prependListener(event, (...args) => { + handler(Cypress, ...args) + }) +}) + +Cypress.on('test:after:run:async', async (test) => { + if (test.title === 'passes 1') { + expect(cypressEventsHandled).to.equal(5) + } else if (test.title === 'passes 2') { + expect(cypressEventsHandled).to.equal(10) + } else if (test.title === 'passes 3') { + expect(cypressEventsHandled).to.equal(15) + } else if (test.title === 'passes 4') { + expect(cypressEventsHandled).to.equal(20) + } else if (test.title === 'passes 5') { + expect(cypressEventsHandled).to.equal(25) + } else if (test.title === 'passes 6') { + expect(cypressEventsHandled).to.equal(!shouldAlwaysResetPage(Cypress.config) ? 29 : 30) + } +}) + +describe('test isolation', () => { + beforeEach(() => { + cy.visit('cypress/e2e/dom-content.html') + }) + + describe('suite 1', { testIsolation: true }, () => { + it('passes 1', () => { + expect(true).to.equal(true) + }) + + it('passes 2', () => { + expect(true).to.equal(true) + }) + }) + + describe('suite 2', { testIsolation: false }, () => { + it('passes 3', () => { + expect(true).to.equal(true) + }) + + it('passes 4', () => { + expect(true).to.equal(true) + }) + }) + + describe('suite 3', { testIsolation: true }, () => { + it('passes 5', () => { + expect(true).to.equal(true) + }) + + it('passes 6', () => { + expect(true).to.equal(true) + }) + }) +}) diff --git a/system-tests/test/test_isolation_spec.ts b/system-tests/test/test_isolation_spec.ts index b202369683..5e9d209749 100644 --- a/system-tests/test/test_isolation_spec.ts +++ b/system-tests/test/test_isolation_spec.ts @@ -12,6 +12,14 @@ describe('Test Isolation', () => { browser: 'chrome', }) + systemTests.it('fires events in the right order with the right arguments when overridden within the spec - run mode', { + project: 'cypress-in-cypress', + spec: 'test-isolation-describe-config.spec.js', + expectedExitCode: 0, + timeout: 20000, + browser: 'chrome', + }) + systemTests.it('fires events in the right order with the right arguments - headed: true - noExit: true', { config: { env: {