diff --git a/circle.yml b/circle.yml index 1f3d76a92d5b..cdf8e3a8038b 100644 --- a/circle.yml +++ b/circle.yml @@ -457,6 +457,11 @@ commands: # https://www.gep13.co.uk/blog/chocolatey-error-hashes-do-not-match [[ $PLATFORM == 'windows' && '<>' == 'chrome' ]] && choco install googlechrome --ignore-checksums || [[ $PLATFORM != 'windows' ]] - run: + # the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars + # are a temporary hack to workaround percy issues with + # PR's not diffing the right base branch due to other problems + # upstream with finalizing builds. These will be removed + # once we implement a more permanent solution. command: | cmd=$([[ <> == 'true' ]] && echo 'yarn percy exec --parallel -- --') || true DEBUG=<> \ @@ -465,6 +470,8 @@ commands: PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \ PERCY_ENABLE=${PERCY_TOKEN:-0} \ PERCY_PARALLEL_TOTAL=-1 \ + PERCY_TARGET_BRANCH="10.0-release" \ + PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \ $cmd yarn workspace @packages/<> cypress:run:<> --browser <> --record --parallel --group <>-<> - store_test_results: path: /tmp/cypress @@ -1034,7 +1041,17 @@ jobs: echo "This is an external PR, cannot access other services" circleci-agent step halt fi - - run: PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 yarn percy build:finalize + - run: + # the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars + # are a temporary hack to workaround percy issues with + # PR's not diffing the right base branch due to other problems + # upstream with finalizing builds. These will be removed + # once we implement a more permanent solution. + command: | + PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \ + PERCY_TARGET_BRANCH="10.0-release" \ + PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \ + yarn percy build:finalize cli-visual-tests: <<: *defaults @@ -1054,10 +1071,17 @@ jobs: path: cli/visual-snapshots - run: name: Upload CLI snapshots for diffing + # the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars + # are a temporary hack to workaround percy issues with + # PR's not diffing the right base branch due to other problems + # upstream with finalizing builds. These will be removed + # once we implement a more permanent solution. command: | PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \ PERCY_ENABLE=${PERCY_TOKEN:-0} \ PERCY_PARALLEL_TOTAL=-1 \ + PERCY_TARGET_BRANCH="10.0-release" \ + PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \ yarn percy snapshot ./cli/visual-snapshots unit-tests: @@ -1325,12 +1349,19 @@ jobs: command: yarn build-for-tests working_directory: packages/reporter - run: + # the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars + # are a temporary hack to workaround percy issues with + # PR's not diffing the right base branch due to other problems + # upstream with finalizing builds. These will be removed + # once we implement a more permanent solution. command: | CYPRESS_KONFIG_ENV=production \ CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \ PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \ PERCY_ENABLE=${PERCY_TOKEN:-0} \ PERCY_PARALLEL_TOTAL=-1 \ + PERCY_TARGET_BRANCH="10.0-release" \ + PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \ yarn percy exec --parallel -- -- \ yarn cypress:run --record --parallel --group reporter working_directory: packages/reporter @@ -1468,11 +1499,19 @@ jobs: - run: name: Run tests # will use PERCY_TOKEN environment variable if available + # + # the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars + # are a temporary hack to workaround percy issues with + # PR's not diffing the right base branch due to other problems + # upstream with finalizing builds. These will be removed + # once we implement a more permanent solution. command: | CYPRESS_KONFIG_ENV=production \ PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \ PERCY_ENABLE=${PERCY_TOKEN:-0} \ PERCY_PARALLEL_TOTAL=-1 \ + PERCY_TARGET_BRANCH="10.0-release" \ + PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \ yarn percy exec --parallel -- -- \ yarn test --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json working_directory: npm/design-system diff --git a/packages/app/cypress/component/support/index.ts b/packages/app/cypress/component/support/index.ts index 91eb2432d983..d705d8632d1d 100644 --- a/packages/app/cypress/component/support/index.ts +++ b/packages/app/cypress/component/support/index.ts @@ -24,7 +24,7 @@ import { createPinia } from '../../../src/store' import { setActivePinia } from 'pinia' import type { Pinia } from 'pinia' import 'cypress-real-events/support' -import installCustomPercyCommand from '@packages/ui-components/cypress/support/customPercyCommand' +import { installCustomPercyCommand } from '@packages/ui-components/cypress/support/customPercyCommand' let pinia: Pinia diff --git a/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts b/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts index 6b7bc8688532..ad3d5f0f4bb1 100644 --- a/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts +++ b/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts @@ -1,4 +1,5 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json' +import { snapshotAUTPanel } from './support/snapshot-aut-panel' describe('Cypress In Cypress', { viewportWidth: 1200 }, () => { beforeEach(() => { @@ -23,19 +24,19 @@ describe('Cypress In Cypress', { viewportWidth: 1200 }, () => { cy.contains('Firefox').should('be.visible') cy.findByTestId('viewport').click() - cy.percySnapshot('browsers open') + snapshotAUTPanel('browsers open') cy.contains('Firefox').should('be.hidden') cy.contains('The viewport determines the width and height of your application. By default the viewport will be 500px by 500px for Component Testing unless specified by a cy.viewport command.') .should('be.visible') - cy.percySnapshot('viewport info open') + snapshotAUTPanel('viewport info open') cy.get('body').click() cy.findByTestId('playground-activator').click() cy.findByTestId('playground-selector').clear().type('#__cy_root') - cy.percySnapshot('cy.get selector') + snapshotAUTPanel('cy.get selector') cy.findByTestId('playground-num-elements').contains('1 Match') @@ -49,7 +50,7 @@ describe('Cypress In Cypress', { viewportWidth: 1200 }, () => { cy.findByTestId('playground-selector').clear().type('Component Test') - cy.percySnapshot('cy.contains selector') + snapshotAUTPanel('cy.contains selector') cy.findByTestId('playground-num-elements').contains('1 Match') }) diff --git a/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts b/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts index 213e5182f0de..3238bee462bf 100644 --- a/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts +++ b/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts @@ -1,4 +1,5 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json' +import { snapshotAUTPanel } from './support/snapshot-aut-panel' describe('Cypress In Cypress', { viewportWidth: 1200 }, () => { beforeEach(() => { @@ -23,19 +24,19 @@ describe('Cypress In Cypress', { viewportWidth: 1200 }, () => { cy.contains('Firefox').should('be.visible') cy.findByTestId('viewport').click() - cy.percySnapshot('browsers open') + snapshotAUTPanel('browsers open') cy.contains('Firefox').should('be.hidden') cy.contains('The viewport determines the width and height of your application. By default the viewport will be 1000px by 660px for End-to-end Testing unless specified by a cy.viewport command.') .should('be.visible') - cy.percySnapshot('viewport info open') + snapshotAUTPanel('viewport info open') cy.get('body').click() cy.findByTestId('playground-activator').click() cy.findByTestId('playground-selector').clear().type('li') - cy.percySnapshot('cy.get selector') + snapshotAUTPanel('cy.get selector') cy.findByTestId('playground-num-elements').contains('3 Matches') @@ -44,7 +45,7 @@ describe('Cypress In Cypress', { viewportWidth: 1200 }, () => { cy.findByTestId('playground-selector').clear().type('Item 1') - cy.percySnapshot('cy.contains selector') + snapshotAUTPanel('cy.contains selector') cy.findByTestId('playground-num-elements').contains('1 Match') diff --git a/packages/runner/cypress/e2e/issues/issue-18042.js b/packages/app/cypress/e2e/runner/issues/issue-18042.cy.js similarity index 53% rename from packages/runner/cypress/e2e/issues/issue-18042.js rename to packages/app/cypress/e2e/runner/issues/issue-18042.cy.js index a668a281a131..4700fce3dbdb 100644 --- a/packages/runner/cypress/e2e/issues/issue-18042.js +++ b/packages/app/cypress/e2e/runner/issues/issue-18042.cy.js @@ -1,15 +1,13 @@ -const helpers = require('../../support/helpers') - -const { createCypress } = helpers -const { runIsolatedCypress } = createCypress() +import { loadSpec } from '../support/spec-loader' // https://github.com/cypress-io/cypress/issues/18042 describe('issue 18042', () => { - beforeEach(function () { - return runIsolatedCypress(`cypress/fixtures/issues/issue-18042.js`) - }) - it('Call count is shown even if cy.stub().log(false)', function () { + loadSpec({ + fileName: 'issue-18042.cy.js', + passCount: 1, + }) + cy.contains('Spies / Stubs (1)').click() cy.get('.call-count').eq(1).should('have.text', '1') }) diff --git a/packages/app/cypress/e2e/runner/issues/issue-9162.cy.js b/packages/app/cypress/e2e/runner/issues/issue-9162.cy.js new file mode 100644 index 000000000000..76b61619975e --- /dev/null +++ b/packages/app/cypress/e2e/runner/issues/issue-9162.cy.js @@ -0,0 +1,14 @@ +import { loadSpec } from '../support/spec-loader' + +// https://github.com/cypress-io/cypress/issues/9162 +describe('issue 9162', () => { + it('tests does not hang even if there is a fail in before().', function () { + loadSpec({ + fileName: 'issue-9162.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.contains('expected true to be false') + }) +}) diff --git a/packages/app/cypress/e2e/runner/reporter.errors.cy.ts b/packages/app/cypress/e2e/runner/reporter.errors.cy.ts index 1f256dd5f23f..5afb130a0c01 100644 --- a/packages/app/cypress/e2e/runner/reporter.errors.cy.ts +++ b/packages/app/cypress/e2e/runner/reporter.errors.cy.ts @@ -1,60 +1,19 @@ +import * as specLoader from './support/spec-loader' import { createVerify, verifyInternalFailure } from './support/verify-failures' -type LoadSpecOptions = { - fileName: string - onLoadStatsMessage: string - hasPreferredIde?: boolean -} - type VerifyFunc = (specTitle: string, verifyOptions: any) => void /** - * Navigates to desired spec file within Cypress app and waits for completion. + * Navigates to desired error spec file within Cypress app and waits for completion. * Returns scoped verify function to aid inner spec validation. */ -function loadSpec (options: LoadSpecOptions): VerifyFunc { +function loadErrorSpec (options: specLoader.LoadSpecOptions): VerifyFunc { const { fileName, - onLoadStatsMessage, hasPreferredIde = false, } = options - cy.scaffoldProject('runner-e2e-specs') - cy.openProject('runner-e2e-specs') - cy.startAppServer() - - cy.withCtx((ctx, options) => { - if (options.hasPreferredIde) { - // set preferred editor to bypass IDE selection dialog - ctx.coreData.localSettings.availableEditors = [ - ...ctx.coreData.localSettings.availableEditors, - { - id: 'test-editor', - binary: '/usr/bin/test-editor', - name: 'Test editor', - }, - ] - - ctx.coreData.localSettings.preferences.preferredEditorBinary = 'test-editor' - } - - ctx.coreData.localSettings.preferences.isSpecsListOpen = false - }, { hasPreferredIde }) - - // directly visiting the spec will sometimes hang, going through - // specs page first to mitigate - // cy.visitApp(`specs/runner?file=cypress/e2e/errors/${fileName}`) - - cy.__incorrectlyVisitAppWithIntercept() - cy.contains('[data-cy=spec-item]', fileName).click() - - cy.location().should((location) => { - expect(location.hash).to.contain(fileName) - }) - - // Wait for specs to complete - cy.findByLabelText('Stats', { timeout: 10000 }) - .get('.failed', { timeout: 10000 }).should('have.text', onLoadStatsMessage) + specLoader.loadSpec(options) // Return scoped verify function with spec options baked in return createVerify({ fileName, hasPreferredIde }) @@ -68,10 +27,10 @@ describe('errors ui', { numTestsKeptInMemory: 1, }, () => { it('assertion failures', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'assertions.cy.js', hasPreferredIde: true, - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('with expect().', { @@ -94,9 +53,9 @@ describe('errors ui', { }) it('assertion failures - no preferred IDE', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'assertions.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('with expect().', { @@ -108,9 +67,9 @@ describe('errors ui', { }) it('exception failures', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'exceptions.cy.js', - onLoadStatsMessage: 'Failed:2', + failCount: 2, }) verify('in spec file', { @@ -126,9 +85,9 @@ describe('errors ui', { }) it('hooks', { viewportHeight: 900 }, () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'hooks.cy.js', - onLoadStatsMessage: 'Failed:1', + failCount: 1, }) // https://github.com/cypress-io/cypress/issues/8214 @@ -142,9 +101,9 @@ describe('errors ui', { }) it('commands', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'commands.cy.js', - onLoadStatsMessage: 'Failed:2', + failCount: 2, }) verify('failure', { @@ -159,9 +118,9 @@ describe('errors ui', { }) it('cy.then', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'then.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure', { @@ -181,9 +140,9 @@ describe('errors ui', { }) it('cy.should', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'should.cy.js', - onLoadStatsMessage: 'Failed:8', + failCount: 8, }) verify('callback assertion failure', { @@ -231,9 +190,9 @@ describe('errors ui', { }) it('cy.each', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'each.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure', { @@ -253,9 +212,9 @@ describe('errors ui', { }) it('cy.spread', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'spread.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure', { @@ -275,9 +234,9 @@ describe('errors ui', { }) it('cy.within', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'within.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure', { @@ -297,9 +256,9 @@ describe('errors ui', { }) it('cy.wrap', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'wrap.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure', { @@ -319,9 +278,9 @@ describe('errors ui', { }) it('cy.visit', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'visit.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('onBeforeLoad assertion failure', { @@ -350,9 +309,9 @@ describe('errors ui', { }) it('cy.intercept', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'intercept.cy.ts', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure in request callback', { @@ -393,9 +352,9 @@ describe('errors ui', { }) it('cy.route', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'route.cy.js', - onLoadStatsMessage: 'Failed:9', + failCount: 9, }) verify('callback assertion failure', { @@ -451,9 +410,9 @@ describe('errors ui', { }) it('cy.server', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'server.cy.js', - onLoadStatsMessage: 'Failed:6', + failCount: 6, }) verify('onAbort assertion failure', { @@ -494,9 +453,9 @@ describe('errors ui', { }) it('cy.readFile', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'readfile.cy.js', - onLoadStatsMessage: 'Failed:1', + failCount: 1, }) verify('existence failure', { @@ -506,9 +465,9 @@ describe('errors ui', { }) it('validation errors', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'validation.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('from cypress', { @@ -530,9 +489,9 @@ describe('errors ui', { }) it('event handlers', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'events.cy.js', - onLoadStatsMessage: 'Failed:4', + failCount: 4, }) verify('event assertion failure', { @@ -557,9 +516,9 @@ describe('errors ui', { }) it('uncaught errors', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'uncaught.cy.js', - onLoadStatsMessage: 'Failed:11', + failCount: 11, }) verify('sync app visit exception', { @@ -706,9 +665,9 @@ describe('errors ui', { }) it('uncaught errors: outside test', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'uncaught_outside_test.cy.js', - onLoadStatsMessage: 'Failed:1', + failCount: 1, }) // NOTE: the following 2 test don't have uncaught: true because we don't @@ -727,9 +686,9 @@ describe('errors ui', { }) it('uncaught errors: outside test only suite', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'uncaught_outside_test_only_suite.cy.js', - onLoadStatsMessage: 'Failed:1', + failCount: 1, }) verify('An uncaught error was detected outside of a test', { @@ -744,9 +703,9 @@ describe('errors ui', { }) it('custom commands', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'custom_commands.cy.js', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure', { @@ -769,9 +728,9 @@ describe('errors ui', { }) it('typescript', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'typescript.cy.ts', - onLoadStatsMessage: 'Failed:3', + failCount: 3, }) verify('assertion failure', { @@ -808,9 +767,9 @@ describe('errors ui', { it('docs url validation', { retries: 1 }, () => { const docsUrl = 'https://on.cypress.io/viewport' - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'docs_url.cy.js', - onLoadStatsMessage: 'Failed:2', + failCount: 2, }) verify('displays as link in interactive mode', { @@ -845,9 +804,9 @@ describe('errors ui', { // instead of the invocation stack. we test this by monkey-patching internal // methods to make them throw an error it('unexpected errors', () => { - const verify = loadSpec({ + const verify = loadErrorSpec({ fileName: 'unexpected.cy.js', - onLoadStatsMessage: 'Failed:1', + failCount: 1, }) // FIXME: the eval doesn't seem to take effect and overwrite the method diff --git a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts new file mode 100644 index 000000000000..e800536d040c --- /dev/null +++ b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts @@ -0,0 +1,115 @@ +import { loadSpec } from './support/spec-loader' + +describe('hooks', { + // Limiting tests kept in memory due to large memory cost + // of nested spec snapshots + numTestsKeptInMemory: 1, +}, () => { + it('displays commands under correct hook', () => { + loadSpec({ + fileName: 'basic.cy.js', + passCount: 2, + }) + + cy.contains('tests 1').click() + + cy.contains('before all').closest('.collapsible').should('contain', 'beforeHook 1') + cy.contains('before each').closest('.collapsible').should('contain', 'beforeEachHook 1') + cy.contains('test body').closest('.collapsible').should('contain', 'testBody 1') + cy.contains('after each').closest('.collapsible').should('contain', 'afterEachHook 1') + + // displays hooks without number when only one of type + cy.contains('before all').should('not.contain', '(1)') + cy.contains('before each').should('not.contain', '(1)') + cy.contains('after each').should('not.contain', '(1)') + + // displays hooks separately with number when more than one of type + cy.contains('tests 1').click() + cy.contains('tests 2').click() + + cy.contains('before all (1)').closest('.collapsible').should('contain', 'beforeHook 2') + cy.contains('before all (2)').closest('.collapsible').should('contain', 'beforeHook 3') + cy.contains('before each (1)').closest('.collapsible').should('contain', 'beforeEachHook 1') + cy.contains('before each (2)').closest('.collapsible').should('contain', 'beforeEachHook 2') + cy.contains('test body').closest('.collapsible').should('contain', 'testBody 2') + cy.contains('after each (1)').closest('.collapsible').should('contain', 'afterEachHook 2') + cy.contains('after each (2)').closest('.collapsible').should('contain', 'afterEachHook 1') + cy.contains('after all (1)').closest('.collapsible').should('contain', 'afterHook 2') + cy.contains('after all (2)').closest('.collapsible').should('contain', 'afterHook 1') + }) + + it('creates open in IDE button', () => { + loadSpec({ + fileName: 'basic.cy.js', + passCount: 2, + hasPreferredIde: true, + }) + + cy.contains('tests 1').click() + + cy.get('.hook-open-in-ide').should('have.length', 4) + + cy.intercept('mutation-OpenFileInIDE', { data: { 'openFileInIDE': true } }).as('OpenIDE') + + cy.contains('Open in IDE').invoke('show').click({ force: true }) + + cy.wait('@OpenIDE').then(({ request }) => { + expect(request.body.variables.input.absolute).to.include('hooks/basic.cy.js') + expect(request.body.variables.input.column).to.eq(Cypress.browser.family === 'firefox' ? 6 : 3) + expect(request.body.variables.input.line).to.eq(2) + }) + }) + + it('does not display commands from skipped tests', () => { + loadSpec({ + fileName: 'skip.cy.js', + passCount: 1, + }) + + // does not display commands from skipped tests + cy.contains('test 1').click() + cy.contains('test 1').parents('.collapsible').first().should('not.contain', 'testBody 1') + cy.contains('test 1').click() + + // displays before hook when following it.skip + // https://github.com/cypress-io/cypress/issues/8086 + cy.contains('test 2').click() + + cy.contains('test 2').parents('.collapsible').first().should('contain', 'before all') + }) + + it('only displays tests with .only', () => { + loadSpec({ + fileName: 'only.cy.js', + passCount: 1, + }) + + cy.contains('test wrapper').parents('.collapsible').first().should(($suite) => { + expect($suite).not.to.contain('test 1') + expect($suite).to.contain('nested suite 1') + expect($suite).to.contain('test 2') + expect($suite).not.to.contain('nested suite 2') + expect($suite).not.to.contain('test 3') + expect($suite).to.contain('nested suite 3') + expect($suite).to.contain('test 4') + }) + + cy.contains('test 2').click() + + cy.contains('test 2').parents('.collapsible').first().should(($test) => { + expect($test).to.contain('before each') + expect($test).to.contain('test body') + }) + }) + + // https://github.com/cypress-io/cypress/issues/8189 + it('can rerun without timeout error leaking into next run (due to run restart)', () => { + loadSpec({ + fileName: 'rerun.cy.js', + passCount: 1, + }) + + // wait until spec has run twice (due to one reload) + cy.window().its('count').should('eq', 2) + }) +}) diff --git a/packages/app/cypress/e2e/runner/retries.ui.cy.ts b/packages/app/cypress/e2e/runner/retries.ui.cy.ts new file mode 100644 index 000000000000..1e57464eb1fd --- /dev/null +++ b/packages/app/cypress/e2e/runner/retries.ui.cy.ts @@ -0,0 +1,358 @@ +import { loadSpec } from './support/spec-loader' +import { snapshotReporter } from './support/snapshot-reporter' + +// Returns wrapped attempt tag found within runnable containing selector +const getAttemptTag = (sel: string) => { + return cy.get(`.test.runnable:contains(${sel}) .attempt-tag`) +} + +describe('runner/cypress retries.ui.spec', { + viewportWidth: 1024, + viewportHeight: 900, + numTestsKeptInMemory: 1, +}, () => { + afterEach(() => { + // @ts-ignore + if (cy.state('test').state === 'passed') { + snapshotReporter() + } + }) + + it('collapses tests that retry and pass', () => { + loadSpec({ + fileName: 'collapse-after-pass.retries.cy.js', + passCount: 2, + failCount: 0, + }) + + cy.get('.test').should('have.length', 2) + }) + + it('collapses prev attempts and keeps final one open on failure', () => { + loadSpec({ + fileName: 'collapse-prev-attempts.retries.cy.js', + passCount: 1, + failCount: 1, + }) + + cy.get('.runnable-err-print').should('be.visible') + }) + + it('can toggle failed prev attempt open and log its error', { viewportHeight: 1200 }, () => { + loadSpec({ + fileName: 'all-retry-one-failure.retries.cy.js', + passCount: 2, + failCount: 1, + }) + + cy.window().then((win) => { + cy.spy(win.console, 'error').as('console_error') + }) + + cy.contains('Attempt 1') + .click() + .closest('.attempt-item') + .find('.runnable-err-print') + .click() + + cy.get('@console_error').should('be.calledWithMatch', 'AssertionError: test 2') + }) + + // TODO: Determine how best to access the inner Cypress instance prior to test execution + // spy on its actions during the test run (https://cypress-io.atlassian.net/browse/UNIFY-1153) + it.skip('opens attempt on each attempt failure for the screenshot, and closes after test passes', () => { + let stub + const cyReject = (fn) => { + return () => { + try { + fn() + } catch (e) { + // @ts-ignore + cy.fail(e) + } + } + } + + loadSpec({ + fileName: 'opens-attempt-for-screenshot.retries.cy.js', + passCount: 3, + failCount: 0, + setup: () => { + let attempt = 0 + + stub = cy.stub().callsFake(cyReject(() => { + attempt++ + expect(cy.$$('.attempt-item > .is-open').length).to.equal(attempt) + })) + + cy.window().then((win) => { + // @ts-ignore + win.Cypress.Screenshot.onAfterScreenshot = stub + }) + }, + }) + + cy.get('.test.runnable:contains(t2)').then(($el) => { + expect($el).not.class('is-open') + expect(stub).callCount(3) + }) + }) + + it('includes routes, spies, hooks, and commands in attempt', () => { + const attemptTag = (sel) => `.attempt-tag:contains(Attempt ${sel})` + + loadSpec({ + fileName: 'includes-all-in-attempt.retries.cy.js', + passCount: 1, + failCount: 0, + }) + + cy.get(attemptTag(1)).click().parentsUntil('.collapsible').last().parent().within(() => { + cy.get('.instruments-container').should('contain', 'Spies / Stubs (1)') + cy.get('.instruments-container').should('contain', 'Routes (1)') + cy.get('.runnable-err').should('contain', 'AssertionError') + }) + + cy.get(attemptTag(2)).click().parentsUntil('.collapsible').last().parent().within(() => { + cy.get('.instruments-container').should('contain', 'Spies / Stubs (2)') + cy.get('.instruments-container').should('contain', 'Routes (2)') + cy.get('.runnable-err').should('contain', 'AssertionError') + }) + + cy.get(attemptTag(3)).parentsUntil('.collapsible').last().parent().within(() => { + cy.get('.instruments-container').should('contain', 'Spies / Stubs (2)') + cy.get('.instruments-container').should('contain', 'Routes (2)') + cy.get('.runnable-err').should('not.be.visible') + }) + }) + + describe('only', () => { + it('test retry with [only]', () => { + loadSpec({ + fileName: 'only.retries.cy.js', + passCount: 1, + failCount: 0, + }) + + cy.contains('test 2') + cy.contains('test 1').should('not.exist') + cy.contains('test 3').should('not.exist') + }) + }) + + describe('beforeAll', () => { + it('tests do not retry when beforeAll fails', () => { + loadSpec({ + fileName: 'before-all-failure.retries.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.contains('Although you have test retries') + }) + + it('before all hooks are not run on the second attempt when fails outside of beforeAll', () => { + loadSpec({ + fileName: 'before-all-called-once.retries.cy.js', + passCount: 1, + failCount: 0, + }) + + cy.contains('test') + cy.contains('after all') + cy.contains('before all').should('not.exist') + }) + }) + + describe('beforeEach', () => { + it('beforeEach hooks retry on failure, but only run same-level afterEach hooks', { viewportHeight: 1550 }, () => { + loadSpec({ + fileName: 'before-each-failure.retries.cy.js', + passCount: 1, + failCount: 0, + }) + + cy.contains('Attempt 1').click() + cy.get('.attempt-1 .hook-item .collapsible:contains(before each)').find('.command-state-failed') + cy.get('.attempt-1 .hook-item .collapsible:contains(before each (2))').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(test body)').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(after each)').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.contains('Attempt 2').click() + cy.get('.attempt-2 .hook-item .collapsible:contains(before each)') + cy.get('.attempt-2 .hook-item .collapsible:contains(before each (2))') + cy.get('.attempt-2 .hook-item .collapsible:contains(before each (3))').find('.command-state-failed') + cy.get('.attempt-2 .hook-item .collapsible:contains(test body)').should('not.exist') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each)') + cy.get('.attempt-2 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.get('.attempt-3 .hook-item .collapsible:contains(before each)') + cy.get('.attempt-3 .hook-item .collapsible:contains(before each (2))') + cy.get('.attempt-3 .hook-item .collapsible:contains(before each (3))') + cy.get('.attempt-3 .hook-item .collapsible:contains(before each (4))') + cy.get('.attempt-3 .hook-item .collapsible:contains(test body)') + cy.get('.attempt-3 .hook-item .collapsible:contains(after each)') + cy.get('.attempt-3 .hook-item .collapsible:contains(after all)') + }) + + it('beforeEach retried tests skip remaining tests in suite', () => { + loadSpec({ + fileName: 'before-each-skip.retries.cy.js', + passCount: 0, + failCount: 1, + pendingCount: 0, + }) + + // ensure the page is loaded before taking snapshot + cy.contains('skips this') + }) + }) + + describe('afterEach', () => { + it('afterEach hooks retry on failure, but only run higher-level afterEach hooks', () => { + const shouldBeOpen = ($el) => cy.wrap($el).parentsUntil('.collapsible').last().parent().should('have.class', 'is-open') + + loadSpec({ + fileName: 'after-each-failure.retries.cy.js', + passCount: 1, + failCount: 0, + }) + + cy.contains('Attempt 1') + .click() + .then(shouldBeOpen) + + cy.get('.attempt-1 .hook-item .collapsible:contains(after each (1))').find('.command-state-failed') + cy.get('.attempt-1 .hook-item .collapsible:contains(after each (2))') + cy.get('.attempt-1 .hook-item .collapsible:contains(after each (3))').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.contains('Attempt 2').click() + .then(shouldBeOpen) + + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (1))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (2))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (3))').find('.command-state-failed') + cy.get('.attempt-2 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.get('.attempt-tag').should('have.length', 3) + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (1))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (2))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (3))') + cy.get('.attempt-3 .hook-item .collapsible:contains(after all)') + }) + + it('afterEach retried tests skip remaining tests in suite', () => { + loadSpec({ + fileName: 'after-each-skip.retries.cy.js', + passCount: 0, + failCount: 1, + pendingCount: 0, + }) + }) + }) + + describe('afterAll', () => { + it('only run afterAll hook on last attempt', () => { + loadSpec({ + fileName: 'after-all-once.retries.cy.js', + passCount: 3, + failCount: 0, + }) + + cy.contains('test 3').click() + getAttemptTag('test 3').first().click() + cy.contains('.attempt-1', 'after all').should('not.exist') + cy.contains('.attempt-2', 'after all') + }) + + it('tests do not retry when afterAll fails', () => { + loadSpec({ + fileName: 'after-all-failure.retries.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.window().then((win) => { + cy.spy(win.console, 'error').as('console_error') + }) + + cy.contains('Although you have test retries') + cy.get('.runnable-err-print').click() + cy.get('@console_error').its('lastCall').should('be.calledWithMatch', 'Error') + }) + }) + + describe('can configure retries', () => { + after(() => { + // @ts-ignore + window.top.__cySkipValidateConfig = false + }) + + const haveCorrectError = ($el) => { + return ( + cy.wrap($el).last().parentsUntil('.collapsible').last().parent() + .find('.runnable-err').should('contain', 'Unspecified AssertionError') + ) + } + + const containText = (text) => { + return (($el) => { + expect($el[0]).property('innerText').contain(text) + }) + } + + // @ts-ignore + window.top.__cySkipValidateConfig = true + + it('via config value', () => { + loadSpec({ + fileName: 'configure-retries.retries.cy.js', + passCount: 0, + failCount: 7, + }) + + getAttemptTag('[no retry]').should('have.length', 1).then(haveCorrectError) + getAttemptTag('[1 retry]').should('have.length', 2).then(haveCorrectError) + getAttemptTag('[2 retries]').should('have.length', 3).then(haveCorrectError) + getAttemptTag('[open mode, no retry]').should('have.length', 1).then(haveCorrectError) + getAttemptTag('[run mode, retry]').should('have.length', 2).then(haveCorrectError) + getAttemptTag('[open mode, 2 retries]').should('have.length', 3).then(haveCorrectError) + getAttemptTag('[set retries on suite]').should('have.length', 2).then(haveCorrectError) + }) + + it('throws when set via this.retries in test', () => { + loadSpec({ + fileName: 'configure-in-test.retries.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.get('.runnable-err') + .should(containText(`it('tries to set mocha retries', { retries: 2 }, () => `)) + }) + + it('throws when set via this.retries in hook', () => { + loadSpec({ + fileName: 'configure-in-hook.retries.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.get('.runnable-err') + .should(containText(`describe('suite 1', { retries: 0 }, () => `)) + }) + + it('throws when set via this.retries in suite', () => { + loadSpec({ + fileName: 'configure-in-suite.retries.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.get('.runnable-err') + .should(containText(`describe('suite 1', { retries: 3 }, () => `)) + }) + }) +}) diff --git a/packages/app/cypress/e2e/runner/runner.ui.cy.ts b/packages/app/cypress/e2e/runner/runner.ui.cy.ts new file mode 100644 index 000000000000..9118f454d6d5 --- /dev/null +++ b/packages/app/cypress/e2e/runner/runner.ui.cy.ts @@ -0,0 +1,242 @@ +import { loadSpec } from './support/spec-loader' + +describe('src/cypress/runner', () => { + describe('tests finish with correct state', () => { + it('simple 1 test', () => { + loadSpec({ + fileName: 'simple-single-test.runner.cy.js', + passCount: 1, + failCount: 0, + }) + }) + + it('simple 1 global test', () => { + loadSpec({ + fileName: 'simple-single-global-test.runner.cy.js', + passCount: 1, + failCount: 0, + }) + }) + + it('simple 3 tests', () => { + loadSpec({ + fileName: 'three-simple-tests.runner.cy.js', + passCount: 3, + failCount: 0, + }) + }) + + it('simple fail', () => { + loadSpec({ + fileName: 'simple-fail.runner.cy.js', + passCount: 0, + failCount: 1, + }) + + // render exactly one error + cy.get('.runnable-err:contains(AssertionError)').should('have.length', 1) + }) + + it('pass fail pass fail', () => { + loadSpec({ + fileName: 'pass-fail-pass-fail.runner.cy.js', + passCount: 2, + failCount: 2, + }) + }) + + it('fail pass', () => { + loadSpec({ + fileName: 'fail-pass.runner.cy.js', + passCount: 1, + failCount: 1, + }) + }) + + it('no tests', () => { + loadSpec({ + fileName: 'no-tests.runner.cy.js', + passCount: 0, + failCount: 0, + }) + + cy.contains('No tests found.').should('be.visible') + cy.contains('p', 'Cypress could not detect tests in this file.').should('be.visible') + }) + + it('executes nested suite', () => { + loadSpec({ + fileName: 'nested-suite.runner.cy.js', + passCount: 3, + failCount: 0, + }) + }) + + it('simple fail, catch cy.on(fail)', () => { + loadSpec({ + fileName: 'catch-fail.runner.cy.js', + passCount: 1, + failCount: 0, + }) + }) + + it('pins cy assertion when clicked', () => { + loadSpec({ + fileName: 'simple-cy-assert.runner.cy.js', + passCount: 1, + failCount: 0, + }) + + cy.contains('li.command-name-assert.command-has-snapshot', 'assert') + .should('not.have.class', 'command-is-pinned') + .click() + .should('have.class', 'command-is-pinned') + }) + + it('renders spec name and runtime in header', () => { + loadSpec({ + fileName: 'simple-cy-assert.runner.cy.js', + passCount: 1, + failCount: 0, + hasPreferredIde: true, + }) + + cy.intercept('mutation-OpenFileInIDE', { data: { 'openFileInIDE': true } }).as('OpenIDE') + + cy.contains('a', 'simple-cy-assert.runner') + .click() + + cy.wait('@OpenIDE').then(({ request }) => { + expect(request.body.variables.input.absolute).to.include('simple-cy-assert.runner.cy.js') + }) + + cy.get('[data-cy="runnable-header"] [data-cy="spec-duration"]').should('exist') + }) + + describe('hook failures', () => { + describe('test failures w/ hooks', () => { + it('test [only]', () => { + loadSpec({ + fileName: 'test-only.runner.cy.js', + passCount: 1, + failCount: 0, + }) + }) + + it('test [pending]', () => { + loadSpec({ + fileName: 'test-pending.runner.cy.js', + passCount: 0, + failCount: 0, + pendingCount: 3, + }) + }) + + it('fail with [before]', () => { + loadSpec({ + fileName: 'fail-with-before.runner.cy.js', + passCount: 1, + failCount: 1, + }) + }) + + it('fail with [after]', () => { + loadSpec({ + fileName: 'fail-with-after.runner.cy.js', + passCount: 1, + failCount: 1, + }) + }) + + it('fail with all hooks', () => { + loadSpec({ + fileName: 'fail-with-all-hooks.runner.cy.js', + passCount: 0, + failCount: 1, + }) + }) + }) + }) + }) + + describe('other specs', () => { + it('simple failing hook spec', () => { + loadSpec({ + fileName: 'failing-hooks.runner.cy.js', + passCount: 1, + failCount: 3, + pendingCount: 1, + }) + + cy.contains('.test', 'never gets here').should('have.class', 'runnable-failed') + cy.contains('.command', 'beforeEach').should('have.class', 'command-state-failed') + cy.contains('.runnable-err', 'beforeEach').scrollIntoView().should('be.visible') + + cy.contains('.test', 'is pending').should('have.class', 'runnable-pending') + + cy.contains('.test', 'fails this').should('have.class', 'runnable-failed') + cy.contains('.command', 'afterEach').should('have.class', 'command-state-failed') + cy.contains('.runnable-err', 'afterEach').scrollIntoView().should('be.visible') + + cy.contains('.test', 'does not run this').should('have.class', 'runnable-processing') + + cy.contains('.test', 'runs this').should('have.class', 'runnable-passed') + + cy.contains('.test', 'fails on this').should('have.class', 'runnable-failed') + cy.contains('.command', 'after').should('have.class', 'command-state-failed') + cy.contains('.runnable-err', 'after').scrollIntoView().should('be.visible') + }) + + it('async timeout spec', () => { + loadSpec({ + fileName: 'async-timeout.runner.cy.js', + passCount: 0, + failCount: 1, + }) + }) + + it('scrolls each command into view', () => { + loadSpec({ + fileName: 'scrolls-command-log.runner.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.get('.command-number:contains(25)').should('be.visible') + }) + + it('file with empty suites only displays no tests found', () => { + loadSpec({ + fileName: 'empty-suites.runner.cy.js', + passCount: 0, + failCount: 0, + }) + + cy.get('.reporter').contains('No tests found') + }) + }) + + describe('reporter interaction', () => { + // https://github.com/cypress-io/cypress/issues/8621 + it('user can stop test execution', () => { + loadSpec({ + fileName: 'stop-execution.runner.cy.js', + passCount: 0, + failCount: 1, + }) + + cy.get('.runnable-err-message').should('not.contain', 'ran afterEach even though specs were stopped') + }) + + // TODO: blocked by UNIFY-1077 + it.skip('supports disabling command log reporter with env var NO_COMMAND_LOG', () => { + loadSpec({ + fileName: 'disabled-command-log.runner.cy.js', + passCount: 0, + failCount: 0, + }) + + cy.get('.reporter').should('not.exist') + }) + }) +}) diff --git a/packages/app/cypress/e2e/runner/sessions.ui.cy.ts b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts new file mode 100644 index 000000000000..276c3cea45a0 --- /dev/null +++ b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts @@ -0,0 +1,71 @@ +import { loadSpec } from './support/spec-loader' +import { snapshotReporter } from './support/snapshot-reporter' + +describe('runner/cypress sessions.ui.spec', { + // Limiting tests kept in memory due to large memory cost + // of nested spec snapshots + numTestsKeptInMemory: 1, +}, () => { + afterEach(function () { + // @ts-ignore + if (cy.state('test').state === 'passed') { + snapshotReporter() + } + }) + + it('empty session with no data', () => { + loadSpec({ + fileName: 'blank_session.cy.js', + passCount: 1, + }) + + cy.get('.sessions-container').click() + .should('contain', 'blank_session') + }) + + it('shows message for new, saved, and recreated session', () => { + loadSpec({ + fileName: 'recreated_session.cy.js', + passCount: 3, + }) + + const clickEl = ($el) => cy.wrap($el).click() + + cy.get('.test').eq(0).then(clickEl) + cy.get('.test').eq(1).then(clickEl) + cy.get('.test').eq(2).then(clickEl) + + cy.get('.sessions-container .collapsible-header[role=button]').eq(0).click() + .should('contain', '1') + + cy.get('.sessions-container .collapsible-header[role=button]').eq(1).click() + .should('contain', '1') + + cy.get('.test').eq(0) + .should('contain', 'Sessions (1)') + .should('contain', 'user1') + .should('contain', '(new)') + + cy.get('.test').eq(1) + .should('contain', 'Sessions (1)') + .should('contain', 'user1') + .should('contain', '(saved)') + + cy.get('.test').eq(2) + .should('contain', 'Sessions (1)') + .should('contain', 'user1') + .should('contain', '(recreated)') + }) + + it('multiple sessions in a test', () => { + loadSpec({ + fileName: 'multiple_sessions.cy.js', + passCount: 1, + }) + + cy.get('.sessions-container').first().click() + .should('contain', 'Sessions (2)') + .should('contain', 'user1') + .should('contain', 'user2') + }) +}) diff --git a/packages/app/cypress/e2e/runner/support/snapshot-reporter.ts b/packages/app/cypress/e2e/runner/support/snapshot-reporter.ts new file mode 100644 index 000000000000..8b8c86ca0a31 --- /dev/null +++ b/packages/app/cypress/e2e/runner/support/snapshot-reporter.ts @@ -0,0 +1,22 @@ +// Takes percy snapshot with navigation/AUT hidden and run duration mocked +export const snapshotReporter = () => { + // @ts-ignore + cy.percySnapshot({ + width: 450, + elementOverrides: { + '.cy-tooltip': true, + '.runnable-header .duration': ($el) => { + $el.text('XX:XX') + }, + '[data-cy=sidebar]': ($el) => { + $el.attr('style', 'display: none !important') + }, + '[data-cy=aut-panel]': ($el) => { + $el.attr('style', 'display: none !important') + }, + '[data-cy=reporter-panel]': ($el) => { + $el.attr('style', 'width: 450px !important') + }, + }, + }) +} diff --git a/packages/app/cypress/e2e/runner/support/spec-loader.ts b/packages/app/cypress/e2e/runner/support/spec-loader.ts new file mode 100644 index 000000000000..c0ef11f88cc3 --- /dev/null +++ b/packages/app/cypress/e2e/runner/support/spec-loader.ts @@ -0,0 +1,80 @@ +export const shouldHaveTestResults = ({ passCount, failCount, pendingCount }) => { + passCount = passCount || '--' + failCount = failCount || '--' + + cy.findByLabelText('Stats', { timeout: 10000 }).within(() => { + cy.get('.passed .num', { timeout: 10000 }).should('have.text', `${passCount}`) + cy.get('.failed .num', { timeout: 10000 }).should('have.text', `${failCount}`) + + if (pendingCount) { + cy.get('.pending .num', { timeout: 10000 }).should('have.text', `${pendingCount}`) + } + }) +} + +export type LoadSpecOptions = { + fileName: string + setup?: () => void + passCount?: number | string + failCount?: number | string + pendingCount?: number | string + hasPreferredIde?: boolean +} + +export function loadSpec (options: LoadSpecOptions): void { + const { + fileName, + setup, + passCount = '--', + failCount = '--', + hasPreferredIde = false, + pendingCount, + } = options + + cy.scaffoldProject('runner-e2e-specs') + cy.openProject('runner-e2e-specs') + cy.startAppServer() + + cy.withCtx((ctx, options) => { + ctx.update((coreData) => { + if (options.hasPreferredIde) { + // set preferred editor to bypass IDE selection dialog + coreData.localSettings.availableEditors = [ + ...ctx.coreData.localSettings.availableEditors, + { + id: 'test-editor', + binary: '/usr/bin/test-editor', + name: 'Test editor', + }, + ] + + coreData.localSettings.preferences.preferredEditorBinary = 'test-editor' + } + + coreData.localSettings.preferences.isSpecsListOpen = false + }) + }, { hasPreferredIde }) + + // TODO: investigate why directly visiting the spec will sometimes hang + // https://cypress-io.atlassian.net/browse/UNIFY-1154 + // cy.__incorrectlyVisitAppWithIntercept(`specs/runner?file=cypress/e2e/errors/${fileName}`) + + cy.__incorrectlyVisitAppWithIntercept() + + if (setup) { + setup() + } + + cy.findByLabelText('Search Specs').type(fileName) + // wait for virtualized spec list to update, there is a chance + // of disconnection otherwise + cy.wait(500) + cy.contains('[data-cy=spec-item]', fileName).click() + + cy.location().should((location) => { + expect(location.hash).to.contain(fileName) + }) + + // Wait for specs to complete + shouldHaveTestResults({ passCount, failCount, pendingCount }) +} diff --git a/packages/app/cypress/e2e/support/snapshot-aut-panel.js b/packages/app/cypress/e2e/support/snapshot-aut-panel.js new file mode 100644 index 000000000000..7218049af949 --- /dev/null +++ b/packages/app/cypress/e2e/support/snapshot-aut-panel.js @@ -0,0 +1,15 @@ +export const snapshotAUTPanel = (name) => { + const hideEl = ($el) => { + $el.attr('style', 'display: none !important') + } + + cy.percySnapshot(name, { + // @ts-ignore + width: 536, + elementOverrides: { + '[data-cy=sidebar]': hideEl, + '[data-cy="reporter-panel"]': hideEl, + '[data-cy="specs-list-panel"]': hideEl, + }, + }) +} diff --git a/packages/app/index.d.ts b/packages/app/index.d.ts index ba2ea2702985..b810e614b50f 100644 --- a/packages/app/index.d.ts +++ b/packages/app/index.d.ts @@ -1,5 +1,6 @@ import type { Socket } from '@packages/socket/lib/browser' import type MobX from 'mobx' +import type { EventManager } from './src/runner/event-manager' export {} @@ -19,7 +20,7 @@ export {} declare global { interface Window { ws?: Socket - + getEventManager: () => EventManager UnifiedRunner: { /** * This is the config served from the back-end. diff --git a/packages/app/src/navigation/SidebarNavigation.vue b/packages/app/src/navigation/SidebarNavigation.vue index a6f7501c6984..99cd4f8b8565 100644 --- a/packages/app/src/navigation/SidebarNavigation.vue +++ b/packages/app/src/navigation/SidebarNavigation.vue @@ -1,5 +1,6 @@