Merge branch 'develop' into fix/config-port-overriding-devserver

This commit is contained in:
Ryan Manuel
2023-08-28 20:32:23 -05:00
committed by GitHub
538 changed files with 26756 additions and 9643 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
# Bump this version to force CI to re-create the cache from scratch.
08-28-23
08-28-23
+14 -18
View File
@@ -30,9 +30,8 @@ mainBuildFilters: &mainBuildFilters
- /^release\/\d+\.\d+\.\d+$/
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'fix/resolve_browser_process_correctly_for_mjs'
- 'chore/bump_loaders_and_optimize_webpack'
- 'bump-circle-cache'
- 'publish-binary'
- 'cacie/chore/capture-metadata'
# 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
@@ -43,8 +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: [ 'fix/resolve_browser_process_correctly_for_mjs', << pipeline.git.branch >> ]
- equal: [ 'bump-circle-cache', << pipeline.git.branch >> ]
- equal: [ cacie/chore/capture-metadata', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -55,9 +53,8 @@ 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: [ 'fix/resolve_browser_process_correctly_for_mjs', << pipeline.git.branch >> ]
- equal: [ 'chore/bump_loaders_and_optimize_webpack', << pipeline.git.branch >> ]
- equal: [ 'astone123/fix-get-published-artifacts', << pipeline.git.branch >> ]
- equal: [ 'publish-binary', << pipeline.git.branch >> ]
- equal: [ cacie/chore/capture-metadata', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -77,8 +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: [ 'fix/resolve_browser_process_correctly_for_mjs', << pipeline.git.branch >> ]
- equal: [ 'bump-circle-cache', << pipeline.git.branch >> ]
- equal: [ cacie/chore/capture-metadata', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -148,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" != "fix/resolve_browser_process_correctly_for_mjs" ]]; 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/handle-304s" ]]; 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
@@ -219,6 +215,7 @@ commands:
command: |
source ./scripts/ensure-node.sh
yarn gulp buildProd
yarn gulp syncCloudValidations
- run:
name: Build packages
command: |
@@ -511,7 +508,7 @@ commands:
# internal PR
CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \
CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \
yarn cypress:run --record --parallel --group 5x-driver-<<parameters.browser>> --browser <<parameters.browser>>
yarn cypress:run --record --parallel --group 5x-driver-<<parameters.browser>> --browser <<parameters.browser>> --runner-ui
else
# external PR
TESTFILES=$(circleci tests glob "cypress/e2e/**/*.cy.*" | circleci tests split --total=$CIRCLE_NODE_TOTAL)
@@ -520,7 +517,7 @@ commands:
if [[ -z "$TESTFILES" ]]; then
echo "Empty list of test files"
fi
yarn cypress:run --browser <<parameters.browser>> --spec $TESTFILES
yarn cypress:run --browser <<parameters.browser>> --spec $TESTFILES --runner-ui
fi
working_directory: packages/driver
- verify-mocha-results
@@ -594,7 +591,7 @@ commands:
if [[ <<parameters.type>> == 'ct' ]]; then
# component tests are located side by side with the source codes.
# for the app component tests, ignore specs that are known to cause failures on contributor PRs (see https://discuss.circleci.com/t/how-to-exclude-certain-files-from-circleci-test-globbing/41028)
TESTFILES=$(find src -regextype posix-extended -name '*.cy.*' -not -regex '.*(FileMatch|PromoAction|SelectorPlayground).cy.*' | circleci tests split --total=$CIRCLE_NODE_TOTAL)
TESTFILES=$(find src -regextype posix-extended -name '*.cy.*' -not -regex '.*(FileMatch|PromoAction|SelectorPlayground|useDurationFormat|useTestingType|SpecPatterns).cy.*' | circleci tests split --total=$CIRCLE_NODE_TOTAL)
else
GLOB="cypress/e2e/**/*cy.*"
TESTFILES=$(circleci tests glob "$GLOB" | circleci tests split --total=$CIRCLE_NODE_TOTAL)
@@ -1850,7 +1847,7 @@ jobs:
PERCY_ENABLE=${PERCY_TOKEN:-0} \
PERCY_PARALLEL_TOTAL=-1 \
yarn percy exec --parallel -- -- \
yarn cypress:run --record --parallel --group reporter
yarn cypress:run --record --parallel --group reporter --runner-ui
working_directory: packages/reporter
- verify-mocha-results
- store_test_results:
@@ -2191,7 +2188,6 @@ jobs:
CYPRESS_PROJECT_ID=$TEST_KITCHENSINK_PROJECT_ID \
CYPRESS_RECORD_KEY=$TEST_KITCHENSINK_RECORD_KEY \
CYPRESS_INTERNAL_ENV=staging \
CYPRESS_video=false \
yarn cypress:run --project /tmp/cypress-example-kitchensink --record
- store-npm-logs
@@ -2807,7 +2803,7 @@ linux-x64-workflow: &linux-x64-workflow
- build
- wait-for-binary-publish:
type: approval
requires:
requires:
- create-and-trigger-packaging-artifacts
- get-published-artifacts:
context:
@@ -2924,7 +2920,7 @@ linux-arm64-workflow: &linux-arm64-workflow
- wait-for-binary-publish:
name: linux-arm64-wait-for-binary-publish
type: approval
requires:
requires:
- linux-arm64-create-and-trigger-packaging-artifacts
- get-published-artifacts:
+2
View File
@@ -40,6 +40,8 @@ module.exports = {
'cli/types/**',
// these fixtures are supposed to fail linting
'npm/eslint-plugin-dev/test/fixtures/**',
// Cloud generated
'system-tests/lib/validations/**',
],
overrides: [
{
+2 -1
View File
@@ -4,4 +4,5 @@
**/.eslintrc text eol=lf
packages/errors/__snapshot-html__/** linguist-generated=true
packages/errors/__snapshot-html__/** linguist-generated=true
system-tests/lib/validations/** linguist-generated=true
+5
View File
@@ -0,0 +1,5 @@
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
#
# Changes to the Module API, after:run, or after:spec results should be
# reviewed by Brian and/or Jennifer
/system-tests/__snapshots__/results_spec.ts.js @brian-mann @jennifer-shehane
+3
View File
@@ -395,3 +395,6 @@ tooling/v8-snapshot/cache/dev-win32
tooling/v8-snapshot/cache/prod-darwin
tooling/v8-snapshot/cache/prod-linux
tooling/v8-snapshot/cache/prod-win32
# Cloud API validations
system-tests/lib/validations
+23 -2
View File
@@ -1,7 +1,24 @@
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
## 12.17.5
## 13.0.0
_Released 08/29/2023 (PENDING)_
_Released 08/22/2023 (PENDING)_
**Breaking Changes:**
- The [`cy.readFile()`](/api/commands/readfile) command is now retry-able as a [query command](https://on.cypress.io/retry-ability). This should not affect any tests using it; the functionality is unchanged. However, it can no longer be overwritten using [`Cypress.Commands.overwrite()`](/api/cypress-api/custom-commands#Overwrite-Existing-Commands). Addressed in [#25595](https://github.com/cypress-io/cypress/pull/25595).
- The [`video`](https://docs.cypress.io/guides/references/configuration#Videos) configuration option now defaults to `false`. Addresses [#26157](https://github.com/cypress-io/cypress/issues/26157).
- The [`videoCompression`](https://docs.cypress.io/guides/references/configuration#Videos) configuration option now defaults to `false`. Addresses [#26160](https://github.com/cypress-io/cypress/issues/26160).
- The [`videoUploadOnPasses`](https://docs.cypress.io/guides/references/configuration#Videos) configuration option has been removed. Please see our [screenshots & videos guide](https://docs.cypress.io/guides/guides/screenshots-and-videos#Delete-videos-for-specs-without-failing-or-retried-tests) on how to accomplish similar functionality. Addresses [#26899](https://github.com/cypress-io/cypress/issues/26899).
- The current spec path is now passed from the AUT iframe using a query parameter rather than a path segment. This allows for requests for assets at relative paths to be correctly forwarded to the dev server. Fixes [#26725](https://github.com/cypress-io/cypress/issues/26725).
- The deprecated configuration option, `nodeVersion` has been removed. Addresses [#27016](https://github.com/cypress-io/cypress/issues/27016).
- The properties and values returned by the [Module API](https://docs.cypress.io/guides/guides/module-api) and included in the arguments of handlers for the [`after:run`](https://docs.cypress.io/api/plugins/after-run-api) and [`after:spec`](https://docs.cypress.io/api/plugins/after-spec-api) have been changed to be more consistent. Addresses [#23805](https://github.com/cypress-io/cypress/issues/23805).
- For Cypress Cloud runs with Test Replay enabled, the Cypress Runner UI is now hidden during the run since the Runner will be visible during Test Replay. As such, if video is recorded (which is now defaulted to `false`) during the run, the Runner will not be visible. In addition, if a runner screenshot (`cy.screenshot({ capture: runner })`) is captured, it will no longer contain the Runner.
- Node 14 support has been removed and Node 16 support has been deprecated. Node 16 may continue to work with Cypress `v13`, but will not be supported moving forward to closer coincide with [Node 16's end-of-life](https://nodejs.org/en/blog/announcements/nodejs16-eol) schedule. It is recommended that users update to at least Node 18.
- The minimum supported Typescript version is `4.x`.
**Features:**
- Consolidates and improves terminal output when uploading test artifacts to Cypress Cloud. Addressed in [#27402](https://github.com/cypress-io/cypress/pull/27402)
**Bugfixes:**
@@ -12,6 +29,10 @@ _Released 08/29/2023 (PENDING)_
- Fixed incorrect type declarations for Cypress and Chai globals that asserted them to be local variables of the global scope rather than properties on the global object. Fixes [#27539](https://github.com/cypress-io/cypress/issues/27539). Fixed in [#27540](https://github.com/cypress-io/cypress/pull/27540).
- Dev Servers will now respect and use the `port` configuration option if present. Fixes [#27675](https://github.com/cypress-io/cypress/issues/27675).
**Dependency Updates:**
- Upgraded [`@cypress/request`](https://www.npmjs.com/package/@cypress/request) from `^2.88.11` to `^3.0.0` to address the [CVE-2023-28155](https://github.com/advisories/GHSA-p8p7-x288-28g6) security vulnerability. Addresses [#27535](https://github.com/cypress-io/cypress/issues/27535). Addressed in [#27495](https://github.com/cypress-io/cypress/pull/27495).
## 12.17.4
_Released 08/15/2023_
+2
View File
@@ -86,6 +86,8 @@ exports['shows help for run --foo 1'] = `
-q, --quiet run quietly, using only the configured reporter
--record [bool] records the run. sends test results, screenshots and videos to Cypress Cloud.
-r, --reporter <reporter> runs a specific mocha reporter. pass a path to use a custom reporter. defaults to "spec"
--runner-ui displays the Cypress Runner UI
--no-runner-ui hides the Cypress Runner UI
-o, --reporter-options <reporter-options> options for the mocha reporter. defaults to "null"
-s, --spec <spec> runs specific spec file(s). defaults to "all"
-t, --tag <tag> named tag(s) for recorded runs in Cypress Cloud
+4
View File
@@ -122,6 +122,8 @@ const descriptions = {
record: 'records the run. sends test results, screenshots and videos to Cypress Cloud.',
reporter: 'runs a specific mocha reporter. pass a path to use a custom reporter. defaults to "spec"',
reporterOptions: 'options for the mocha reporter. defaults to "null"',
runnerUi: 'displays the Cypress Runner UI',
noRunnerUi: 'hides the Cypress Runner UI',
spec: 'runs specific spec file(s). defaults to "all"',
tag: 'named tag(s) for recorded runs in Cypress Cloud',
version: 'prints Cypress version',
@@ -252,6 +254,8 @@ const addCypressRunCommand = (program) => {
.option('-q, --quiet', text('quiet'))
.option('--record [bool]', text('record'), coerceFalse)
.option('-r, --reporter <reporter>', text('reporter'))
.option('--runner-ui', text('runnerUi'))
.option('--no-runner-ui', text('noRunnerUi'))
.option('-o, --reporter-options <reporter-options>', text('reporterOptions'))
.option('-s, --spec <spec>', text('spec'))
.option('-t, --tag <tag>', text('tag'))
+4
View File
@@ -133,6 +133,10 @@ const processRunOptions = (options = {}) => {
args.push('--reporter-options', options.reporterOptions)
}
if (options.runnerUi != null) {
args.push('--runner-ui', options.runnerUi)
}
// if we have specific spec(s) push that into the args
if (options.spec) {
args.push('--spec', options.spec)
+1
View File
@@ -225,6 +225,7 @@ const parseOpts = (opts) => {
'reporter',
'reporterOptions',
'record',
'runnerUi',
'runProject',
'spec',
'tag')
+2 -2
View File
@@ -20,7 +20,7 @@
"unit": "cross-env BLUEBIRD_DEBUG=1 NODE_ENV=test mocha --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json"
},
"dependencies": {
"@cypress/request": "2.88.12",
"@cypress/request": "^3.0.0",
"@cypress/xvfb": "^1.2.4",
"@types/node": "^16.18.39",
"@types/sinonjs__fake-timers": "8.1.1",
@@ -124,7 +124,7 @@
"cypress": "bin/cypress"
},
"engines": {
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
},
"types": "types",
"exports": {
+8 -5
View File
@@ -1,6 +1,7 @@
const _ = require('lodash')
const path = require('path')
const shell = require('shelljs')
const minimist = require('minimist')
const fs = require('../lib/fs')
@@ -24,7 +25,7 @@ function getStdout (cmd) {
return shell.exec(cmd).trim()
}
function preparePackageForNpmRelease (json) {
function preparePackageForNpmRelease (json, branchName) {
// modify the existing package.json
// to prepare it for releasing to npm
delete json.devDependencies
@@ -36,7 +37,7 @@ function preparePackageForNpmRelease (json) {
_.extend(json, {
version,
buildInfo: {
commitBranch: process.env.CIRCLE_BRANCH || getStdout('git branch --show-current'),
commitBranch: branchName || process.env.CIRCLE_BRANCH || getStdout('git branch --show-current'),
commitSha: getStdout('git rev-parse HEAD'),
commitDate: new Date(getStdout('git show -s --format=%ci')).toISOString(),
stable: false,
@@ -57,9 +58,9 @@ function preparePackageForNpmRelease (json) {
return json
}
function makeUserPackageFile () {
function makeUserPackageFile (branchName) {
return fs.readJsonAsync(packageJsonSrc)
.then(preparePackageForNpmRelease)
.then((json) => preparePackageForNpmRelease(json, branchName))
.then((json) => {
return fs.outputJsonAsync(packageJsonDest, json, {
spaces: 2,
@@ -71,7 +72,9 @@ function makeUserPackageFile () {
module.exports = makeUserPackageFile
if (!module.parent) {
makeUserPackageFile()
const args = minimist(process.argv)
makeUserPackageFile(args.branch)
.catch((err) => {
/* eslint-disable no-console */
console.error('Could not write user package file')
+12 -2
View File
@@ -474,15 +474,25 @@ describe('cli', () => {
expect(run.start).to.be.calledWith({ ciBuildId: '123', group: 'staging' })
})
it('call run with --auto-cancel-after-failures', () => {
it('calls run with --auto-cancel-after-failures', () => {
this.exec('run --auto-cancel-after-failures 4')
expect(run.start).to.be.calledWith({ autoCancelAfterFailures: '4' })
})
it('call run with --auto-cancel-after-failures with false', () => {
it('calls run with --auto-cancel-after-failures with false', () => {
this.exec('run --auto-cancel-after-failures false')
expect(run.start).to.be.calledWith({ autoCancelAfterFailures: 'false' })
})
it('calls run with --runner-ui', () => {
this.exec('run --runner-ui')
expect(run.start).to.be.calledWith({ runnerUi: true })
})
it('calls run with --no-runner-ui', () => {
this.exec('run --no-runner-ui')
expect(run.start).to.be.calledWith({ runnerUi: false })
})
})
context('cypress open', () => {
+1
View File
@@ -105,6 +105,7 @@ describe('cypress', function () {
.then((args) => {
expect(args.spec).to.equal('foo')
expect(args.autoCancelAfterFailures).to.equal(4)
expect(args.runnerUi).to.be.undefined
})
})
+9
View File
@@ -234,5 +234,14 @@ describe('exec run', function () {
])
})
})
it('spawns with --runner-ui', function () {
return run.start({ runnerUi: true })
.then(() => {
expect(spawn.start).to.be.calledWith([
'--run-project', process.cwd(), '--runner-ui', true,
])
})
})
})
})
+69 -77
View File
@@ -7,13 +7,6 @@
// but for now describe it as an ambient module
declare namespace CypressCommandLine {
type HookName = 'before' | 'beforeEach' | 'afterEach' | 'after'
interface TestError {
name: string
message: string
stack: string
}
/**
* All options that one can pass to "cypress.run"
* @see https://on.cypress.io/module-api#cypress-run
@@ -99,6 +92,10 @@ declare namespace CypressCommandLine {
* Specify the number of failures to cancel a run being recorded to the Cloud or false to disable auto-cancellation.
*/
autoCancelAfterFailures: number | false
/**
* Whether to display the Cypress Runner UI
*/
runnerUi: boolean
}
/**
@@ -174,9 +171,9 @@ declare namespace CypressCommandLine {
* Cypress single test result
*/
interface TestResult {
duration: number
title: string[]
state: string
body: string
/**
* Error string as it's presented in console if the test fails
*/
@@ -186,20 +183,6 @@ declare namespace CypressCommandLine {
interface AttemptResult {
state: string
error: TestError | null
wallClockStartedAt: dateTimeISO
wallClockDuration: ms
videoTimestamp: ms
screenshots: ScreenshotInformation[]
}
/**
* Information about a single "before", "beforeEach", "afterEach" and "after" hook.
*/
interface HookInformation {
hookName: HookName
title: string[]
body: string
}
/**
@@ -216,25 +199,34 @@ declare namespace CypressCommandLine {
width: pixels
}
interface SpecResult {
/**
* resolved filename of the spec
*/
absolute: string
/**
* file extension like ".js"
*/
fileExtension: string
/**
* file name without extension like "spec"
*/
fileName: string
/**
* filename like "spec.js"
*/
name: string
/**
* name relative to the project root, like "cypress/integration/spec.js"
*/
relative: string
}
/**
* Cypress test run result for a single spec.
*/
interface RunResult {
/**
* Accurate test results collected by Cypress.
*/
stats: {
suites: number
tests: number
passes: number
pending: number
skipped: number
failures: number
startedAt: dateTimeISO
endedAt: dateTimeISO
duration: ms
wallClockDuration?: number
}
error: string | null
/**
* Reporter name like "spec"
*/
@@ -244,30 +236,31 @@ declare namespace CypressCommandLine {
* the properties. Usually this object has suites, tests, passes, etc
*/
reporterStats: object
hooks: HookInformation[]
tests: TestResult[]
error: string | null
video: string | null
screenshots: ScreenshotInformation[]
/**
* Accurate test results collected by Cypress.
*/
stats: {
duration?: ms
endedAt: dateTimeISO
failures: number
passes: number
pending: number
startedAt: dateTimeISO
suites: number
tests: number
}
/**
* information about the spec test file.
*/
spec: {
/**
* filename like "spec.js"
*/
name: string
/**
* name relative to the project root, like "cypress/integration/spec.js"
*/
relative: string
/**
* resolved filename of the spec
*/
absolute: string
relativeToCommonRoot: string
}
shouldUploadVideo: boolean
skippedSpec: boolean
*/
spec: SpecResult
tests: TestResult[]
video: string | null
}
type PublicConfig = Omit<Cypress.ResolvedConfigOptions, 'additionalIgnorePattern' | 'autoOpen' | 'browser' | 'browsers' | 'browserUrl' | 'clientRoute' | 'cypressEnv' | 'devServerPublicPathRoute' | 'morgan' | 'namespace' | 'proxyServer' | 'proxyUrl' | 'rawJson' | 'remote' | 'repoRoot' | 'report' | 'reporterRoute' | 'reporterUrl' | 'resolved' | 'setupNodeEvents' | 'socketId' | 'socketIoCookie' | 'socketIoRoute' | 'specs' | 'state' | 'supportFolder'> & {
browsers: Cypress.PublicBrowser[]
cypressInternalEnv: string
}
/**
@@ -275,29 +268,28 @@ declare namespace CypressCommandLine {
* @see https://on.cypress.io/module-api
*/
interface CypressRunResult {
status: 'finished'
startedTestsAt: dateTimeISO
browserName: string
browserPath: string
browserVersion: string
config: PublicConfig
cypressVersion: string
endedTestsAt: dateTimeISO
totalDuration: ms
totalSuites: number
totalTests: number
osName: string
osVersion: string
runs: RunResult[]
/**
* If Cypress test run was recorded, full url will be provided.
* @see https://on.cypress.io/cloud-introduction
*/
runUrl?: string
startedTestsAt: dateTimeISO
totalDuration: number
totalFailed: number
totalPassed: number
totalPending: number
totalSkipped: number
/**
* If Cypress test run is being recorded, full url will be provided.
* @see https://on.cypress.io/dashboard-introduction
*/
runUrl?: string
runs: RunResult[]
browserPath: string
browserName: string
browserVersion: string
osName: string
osVersion: string
cypressVersion: string
config: Cypress.ResolvedConfigOptions
totalSuites: number
totalTests: number
}
/**
+19 -13
View File
@@ -134,6 +134,19 @@ declare namespace Cypress {
unsupportedVersion?: boolean
}
/**
* Browser that's exposed in public APIs
*/
interface PublicBrowser {
channel: BrowserChannel
displayName: string
family: string
majorVersion?: string | number | null
name: BrowserName
path: string
version: string
}
interface Ensure {
/**
* Throws an error if `subject` is not one of the passed in `type`s.
@@ -2936,18 +2949,13 @@ declare namespace Cypress {
* @default "cypress/downloads"
*/
downloadsFolder: string
/**
* If set to `system`, Cypress will try to find a `node` executable on your path to use when executing your plugins. Otherwise, Cypress will use the Node version bundled with Cypress.
* @default "bundled"
*/
nodeVersion: 'system' | 'bundled'
/**
* The application under test cannot redirect more than this limit.
* @default 20
*/
redirectionLimit: number
/**
* If `nodeVersion === 'system'` and a `node` executable is found, this will be the full filesystem path to that executable.
* If a `node` executable is found, this will be the full filesystem path to that executable.
* @default null
*/
resolvedNodePath: string
@@ -3016,15 +3024,10 @@ declare namespace Cypress {
*/
videoCompression: number | boolean
/**
* Whether Cypress will record a video of the test run when running headlessly.
* @default true
* Whether Cypress will record a video of the test run when executing in run mode.
* @default false
*/
video: boolean
/**
* Whether Cypress will upload the video to Cypress Cloud even if all tests are passing. This applies only when recording your runs to Cypress Cloud. Turn this off if you'd like the video uploaded only when there are failing tests.
* @default true
*/
videoUploadOnPasses: boolean
/**
* Whether Chrome Web Security for same-origin policy and insecure mixed content is enabled. Read more about this here
* @default true
@@ -3265,6 +3268,9 @@ declare namespace Cypress {
socketIoRoute: string
spec: Cypress['spec'] | null
specs: Array<Cypress['spec']>
protocolEnabled: boolean
hideCommandLog: boolean
hideRunnerUi: boolean
}
interface SuiteConfigOverrides extends Partial<
+1 -1
View File
@@ -4,7 +4,7 @@
// Mike Woudenberg <https://github.com/mikewoudenberg>
// Robbert van Markus <https://github.com/rvanmarkus>
// Nicholas Boll <https://github.com/nicholasboll>
// TypeScript Version: 3.9
// TypeScript Version: 4.3
// Updated by the Cypress team: https://www.cypress.io/about/
/// <reference path="./cy-blob-util.d.ts" />
+1 -6
View File
@@ -43,12 +43,7 @@ cypress.run({}).then((results) => {
// the caller can determine if Cypress ran or failed to launch
cypress.run().then(results => {
if (results.status === 'failed') {
results // $ExpectType CypressFailedRunResult
} else {
results // $ExpectType CypressRunResult
results.status // $ExpectType "finished"
}
results // $ExpectType CypressRunResult | CypressFailedRunResult
})
const config = defineConfig({
+10
View File
@@ -0,0 +1,10 @@
# Protocol Development
In production, the capture code used to capture and communicate test data will be retrieved from the Cloud. However, in order to develop the capture code locally, developers will:
* Clone the `cypress-services` repo
* Run `yarn`
* Run `yarn watch` in `packages/app-capture-protocol`
* Clone the `cypress` repo
* Run `yarn`
* Execute `CYPRESS_LOCAL_PROTOCOL_PATH=path/to/cypress-services/packages/app-capture-protocol/dist/index.js CYPRESS_INTERNAL_ENV=staging yarn cypress:run --record --key <record_key> --project <path/to/project>` on a project in record mode
+5 -3
View File
@@ -48,7 +48,7 @@ The `@cypress/`-namespaced NPM packages that live inside the [`/npm`](../npm) di
- [`cypress-documentation`](https://github.com/cypress-io/cypress-documentation)
- [`cypress-docker-images`](https://github.com/cypress-io/cypress-docker-images)
- [cypress-io/release-automations][release-automations]
If you don't have access to 1Password, ask a team member who has done a deploy.
@@ -88,7 +88,7 @@ _Note: It is advisable to notify the team that the `develop` branch is locked do
2. Ensure all changes to the links manifest to [`on.cypress.io`](https://github.com/cypress-io/cypress-services/tree/develop/packages/on) have been merged to `develop` and deployed.
3. Create a Release PR -
3. Create a Release PR -
Bump, submit, get approvals on, and merge a new PR. This PR should:
- Bump the Cypress `version` in [`package.json`](package.json)
- Bump the [`packages/example`](../packages/example) dependency if there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version
@@ -185,7 +185,9 @@ _Note: It is advisable to notify the team that the `develop` branch is locked do
23. Notify the team that `develop` is reopen, and post a message to the Releases Slack channel with a link to the changelog.
24. Check all `cypress-test-*` and `cypress-example-*` repositories, and if there is a branch named `x.y.z` for testing the features or fixes from the newly published version `x.y.z`, update that branch to refer to the newly published NPM version in `package.json`. Then, get the changes approved and merged into that project's main branch. For projects without a `x.y.z` branch, you can go to the Renovate dependency issue and check the box next to `Update dependency cypress to X.Y.Z`. It will automatically create a PR. Once it passes, you can merge it. Try updating at least the following projects:
24. If utilizing the `SKIP_RELEASE_CHANGELOG_VALIDATION_FOR_BRANCHES` to override and skip changelog validation for this release, change its value as needed or delete it from CircleCI so that subsequent releases and PRs will go through changelog validation.
25. Check all `cypress-test-*` and `cypress-example-*` repositories, and if there is a branch named `x.y.z` for testing the features or fixes from the newly published version `x.y.z`, update that branch to refer to the newly published NPM version in `package.json`. Then, get the changes approved and merged into that project's main branch. For projects without a `x.y.z` branch, you can go to the Renovate dependency issue and check the box next to `Update dependency cypress to X.Y.Z`. It will automatically create a PR. Once it passes, you can merge it. Try updating at least the following projects:
- [cypress-example-todomvc](https://github.com/cypress-io/cypress-example-todomvc/issues/99)
- [cypress-realworld-app](https://github.com/cypress-io/cypress-realworld-app/issues/41)
- [cypress-example-recipes](https://github.com/cypress-io/cypress-example-recipes/issues/225)
-1
View File
@@ -11,5 +11,4 @@ module.exports = defineConfig({
specPattern: '**/spec.js',
},
fixturesFolder: false,
video: false,
})
+1 -1
View File
@@ -57,7 +57,7 @@ export function setupHooks (optionalCallback?: Function) {
})
// @ts-ignore
Cypress.on('test:before:run', () => {
Cypress.on('test:before:after:run:async', () => {
optionalCallback?.()
})
}
-1
View File
@@ -1,7 +1,6 @@
module.exports = {
'viewportWidth': 400,
'viewportHeight': 400,
'video': false,
'projectId': 'z9dxah',
'env': {
'reactDevtools': true,
@@ -21,7 +21,7 @@ describe('React Memory Router', () => {
cy.log('**About** component')
cy.contains('h2', 'About')
// because the routing is in memory, the URL stays at the spec filename
cy.location('pathname').should('match', /in-memory.cy.jsx$/)
cy.location('search').should('match', /in-memory.cy.jsx$/)
// Go to home route
cy.contains('a', 'Home').click()
@@ -29,7 +29,7 @@ describe('React Memory Router', () => {
cy.log('**Home** component')
cy.contains('h2', 'Home') // from the "Home" component
// still at the spec url
cy.location('pathname').should('match', /in-memory.cy.jsx$/)
cy.location('search').should('match', /in-memory.cy.jsx$/)
// Go to about route
cy.log('back to **About** component')
@@ -37,6 +37,6 @@ describe('React Memory Router', () => {
cy.contains('h2', 'About')
// still at the spec url
cy.location('pathname').should('match', /in-memory.cy.jsx$/)
cy.location('search').should('match', /in-memory.cy.jsx$/)
})
})
@@ -27,7 +27,7 @@ it('has the same window from the component as from test', () => {
mount(<Component />)
cy.contains('component')
cy.window()
.its('location.pathname')
.its('location.search')
// this filename
.should('match', /window.cy.jsx$/)
-1
View File
@@ -3,7 +3,6 @@ import { defineConfig } from 'cypress'
export default defineConfig({
'viewportWidth': 500,
'viewportHeight': 500,
'video': false,
'responseTimeout': 2500,
'projectId': '134ej7',
'experimentalFetchPolyfill': true,
+1
View File
@@ -68,6 +68,7 @@ We then merge the sourced config with the user's webpack config, and layer on ou
| --------------------------- | ------- |
| <= v1 | <= v9 |
| >= v2 | >= v10 |
| >= v4 | >= v13 |
## License
@@ -68,4 +68,15 @@ describe('Config options', () => {
cy.contains('New.cy.js').click()
cy.waitForSpecToFinish({ passCount: 2 })
})
it('supports loading assets via relative urls', () => {
cy.scaffoldProject('webpack-dev-server-relative')
cy.openProject('webpack-dev-server-relative')
cy.startAppServer('component')
cy.visitApp()
cy.contains('relative-url.cy.jsx').click()
cy.waitForSpecToFinish()
cy.get('.passed > .num').should('contain', 1)
})
})
@@ -239,6 +239,7 @@ function checkSWC (
// "resolvedNodePath" is only set when using the user's Node.js, which is required to compile Next.js with SWC optimizations
// If it is not set, they have either explicitly set "nodeVersion" to "bundled" or are are using Cypress < 9.0.0 where it was set to "bundled" by default
// @ts-expect-error nodeVersion has been removed as of 13.0.0 however this plugin can be used with many versions of cypress
if (hasSWCLoader && cypressConfig.nodeVersion === 'bundled') {
throw new Error(`Cypress cannot compile your Next.js application when "nodeVersion" is set to "bundled". Please remove this option from your Cypress configuration file.`)
}
+2 -2
View File
@@ -18,7 +18,7 @@ const makeImport = (file: Cypress.Cypress['spec'], filename: string, chunkName:
const magicComments = chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''
return `"${filename}": {
shouldLoad: () => decodeURI(document.location.pathname).includes("${file.absolute}"),
shouldLoad: () => new URLSearchParams(document.location.search).get("specPath") === "${file.absolute}",
load: () => import("${file.absolute}" ${magicComments}),
absolute: "${file.absolute.split(path.sep).join(path.posix.sep)}",
relative: "${file.relative.split(path.sep).join(path.posix.sep)}",
@@ -33,7 +33,7 @@ const makeImport = (file: Cypress.Cypress['spec'], filename: string, chunkName:
* @returns {Record<string, ReturnType<makeImport>}
* {
* "App.spec.js": {
* shouldLoad: () => document.location.pathname.includes("cypress/component/App.spec.js"),
* shouldLoad: () => (new URL(document.location)).searchParams.get("specPath") === "cypress/component/App.spec.js",
* load: () => {
* return import("/Users/projects/my-app/cypress/component/App.spec.js" \/* webpackChunkName: "spec-0" *\/)
* },
@@ -91,9 +91,6 @@ exports.runTest = async (options = {}) => {
spec: opts.spec,
browser: opts.browser,
exit: opts.exit,
config: {
video: false,
},
dev: true,
})
.finally(() => {
+3 -2
View File
@@ -72,8 +72,8 @@
"devDependencies": {
"@aws-sdk/credential-providers": "3.53.0",
"@cypress/questions-remain": "1.0.1",
"@cypress/request": "2.88.12",
"@cypress/request-promise": "4.2.7",
"@cypress/request": "^3.0.0",
"@cypress/request-promise": "^5.0.0",
"@electron/fuses": "1.6.1",
"@electron/notarize": "^2.1.0",
"@fellow/eslint-plugin-coffee": "0.4.13",
@@ -94,6 +94,7 @@
"@percy/cli": "1.2.0",
"@semantic-release/changelog": "5.0.1",
"@semantic-release/git": "9.0.0",
"@types/better-sqlite3": "^7.6.3",
"@types/bluebird": "3.5.29",
"@types/chai-enzyme": "0.6.7",
"@types/classnames": "2.2.9",
-1
View File
@@ -7,7 +7,6 @@ export default defineConfig({
runMode: 2,
openMode: 0,
},
videoCompression: false, // turn off video compression for CI
reporter: '../../node_modules/cypress-multi-reporters/index.js',
reporterOptions: {
configFile: '../../mocha-reporter-config.json',
@@ -178,63 +178,6 @@ describe('Cypress In Cypress CT', { viewportWidth: 1500, defaultCommandTimeout:
expect(ctx.actions.project.initializeActiveProject).to.be.called
})
})
it('moves away from runner and back, disconnects websocket and reconnects it correctly', () => {
cy.openProject('cypress-in-cypress', ['--component'])
cy.startAppServer('component')
cy.visitApp()
cy.contains('TestComponent.spec').click()
cy.waitForSpecToFinish()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')
cy.get('.passed > .num').should('contain', 1)
cy.get('.failed > .num').should('contain', '--')
cy.findByTestId('sidebar-link-runs-page').click()
cy.get('[data-cy="app-header-bar"]').findByText('Runs').should('be.visible')
cy.findByTestId('sidebar-link-specs-page').click()
cy.get('[data-cy="app-header-bar"]').findByText('Specs').should('be.visible')
cy.contains('TestComponent.spec').click()
cy.waitForSpecToFinish()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test component')
cy.window().then((win) => {
const connected = () => win.ws?.connected
win.ws?.close()
cy.wrap({
connected,
}).invoke('connected').should('be.false')
win.ws?.connect()
cy.wrap({
connected,
}).invoke('connected').should('be.true')
})
cy.withCtx(async (ctx, o) => {
await ctx.actions.file.writeFileInProject(o.path, `
import React from 'react'
import { mount } from 'cypress/react'
describe('TestComponent', () => {
it('renders the new test component', () => {
mount(<div>Component Test</div>)
cy.contains('Component Test').should('be.visible')
})
})
`)
}, { path: getPathForPlatform('src/TestComponent.spec.jsx') })
cy.get('[data-model-state="passed"]').should('contain', 'renders the new test component')
cy.get('.passed > .num').should('contain', 1)
cy.get('.failed > .num').should('contain', '--')
})
})
context('custom config', () => {
@@ -231,55 +231,6 @@ describe('Cypress In Cypress E2E', { viewportWidth: 1500, defaultCommandTimeout:
cy.get('[data-model-state="passed"]').should('contain', 'expected true to be true')
})
it('moves away from runner and back, disconnects websocket and reconnects it correctly', () => {
cy.visitApp()
cy.contains('dom-content.spec').click()
cy.waitForSpecToFinish()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test content')
cy.get('.passed > .num').should('contain', 1)
cy.get('.failed > .num').should('contain', '--')
cy.findByTestId('sidebar-link-runs-page').click()
cy.get('[data-cy="app-header-bar"]').findByText('Runs').should('be.visible')
cy.findByTestId('sidebar-link-specs-page').click()
cy.get('[data-cy="app-header-bar"]').findByText('Specs').should('be.visible')
cy.contains('dom-content.spec').click()
cy.waitForSpecToFinish()
cy.get('[data-model-state="passed"]').should('contain', 'renders the test content')
cy.window().then((win) => {
const connected = () => win.ws?.connected
win.ws?.close()
cy.wrap({
connected,
}).invoke('connected').should('be.false')
win.ws?.connect()
cy.wrap({
connected,
}).invoke('connected').should('be.true')
})
cy.withCtx(async (ctx, o) => {
await ctx.actions.file.writeFileInProject(o.path, `
describe('Dom Content', () => {
it('renders the new test content', () => {
cy.visit('cypress/e2e/dom-content.html')
})
})
`)
}, { path: getPathForPlatform('cypress/e2e/dom-content.spec.js') })
cy.get('[data-model-state="passed"]').should('contain', 'renders the new test content')
cy.get('.passed > .num').should('contain', 1)
cy.get('.failed > .num').should('contain', '--')
})
describe('accessibility', () => {
it('has no axe violations in specs list panel', () => {
cy.visitApp()
@@ -1,4 +1,5 @@
import { CY_IN_CY_SIMULATE_RUN_MODE } from '@packages/types'
import type { ReceivedCypressOptions } from '@packages/types'
describe('Cypress In Cypress - run mode', { viewportWidth: 1200 }, () => {
it('e2e run mode spec runner header is correct', () => {
@@ -60,21 +61,18 @@ describe('Cypress In Cypress - run mode', { viewportWidth: 1200 }, () => {
// cy.percySnapshot() // TODO: restore when Percy CSS is fixed. See https://github.com/cypress-io/cypress/issues/23435
})
it('hides reporter when NO_COMMAND_LOG is set in run mode', () => {
it('hides the command log when hideCommandLog is set in run mode', () => {
cy.scaffoldProject('cypress-in-cypress')
cy.findBrowsers()
cy.openProject('cypress-in-cypress')
cy.startAppServer()
cy.withCtx(async (ctx, o) => {
const config = await ctx.project.getConfig()
const config = ctx._apis.projectApi.getConfig()
o.sinon.stub(ctx.project, 'getConfig').resolves({
o.sinon.stub(ctx._apis.projectApi, 'getConfig').returns({
...config,
env: {
...config.env,
NO_COMMAND_LOG: 1,
},
})
hideCommandLog: true,
} as ReceivedCypressOptions)
})
cy.visitApp(`/specs/runner?file=cypress/e2e/dom-content.spec.js&${CY_IN_CY_SIMULATE_RUN_MODE}`)
@@ -85,4 +83,29 @@ describe('Cypress In Cypress - run mode', { viewportWidth: 1200 }, () => {
cy.findByTestId('reporter-panel').should('not.be.visible')
cy.findByTestId('sidebar').should('not.exist')
})
it('hides the runner when hideRunnerUi is set in run mode', () => {
cy.scaffoldProject('cypress-in-cypress')
cy.findBrowsers()
cy.openProject('cypress-in-cypress')
cy.startAppServer()
cy.withCtx(async (ctx, o) => {
const config = ctx._apis.projectApi.getConfig()
o.sinon.stub(ctx._apis.projectApi, 'getConfig').returns({
...config,
hideCommandLog: true,
hideRunnerUi: true,
} as ReceivedCypressOptions)
})
cy.visitApp(`/specs/runner?file=cypress/e2e/dom-content.spec.js&${CY_IN_CY_SIMULATE_RUN_MODE}`)
cy.contains('http://localhost:4455/cypress/e2e/dom-content.html').should('not.exist')
cy.findByLabelText('Stats').should('not.exist')
cy.findByTestId('specs-list-panel').should('not.be.visible')
cy.findByTestId('reporter-panel').should('not.be.visible')
cy.findByTestId('sidebar').should('not.exist')
cy.get('#spec-runner-header').should('not.exist')
})
})
@@ -1,3 +1,4 @@
import type { ReceivedCypressOptions } from '@packages/types'
import type { DraggablePanel } from '../../src/runner/useRunnerStyle'
const testingTypes = ['component', 'e2e'] as const
@@ -36,7 +37,7 @@ describe('Cypress in Cypress', { viewportWidth: 1500, defaultCommandTimeout: 100
cy.waitForSpecToFinish()
cy.withCtx((ctx) => {
ctx.coreData.servers.appSocketServer?.emit('automation:disconnected')
ctx.coreData.servers.cdpSocketServer?.emit('automation:disconnected')
})
cy.contains('h3', 'The Cypress extension has disconnected')
@@ -237,21 +238,18 @@ describe('Cypress in Cypress', { viewportWidth: 1500, defaultCommandTimeout: 100
cy.get('[data-cy="playground-num-elements"]').contains('1 match')
})
it(`hides reporter when NO_COMMAND_LOG is set in open mode for ${testingType}`, () => {
it(`hides the command log when hideCommandLog is set in open mode for ${testingType}`, () => {
cy.scaffoldProject('cypress-in-cypress')
cy.findBrowsers()
cy.openProject('cypress-in-cypress')
cy.startAppServer()
cy.withCtx(async (ctx, o) => {
const config = await ctx.project.getConfig()
const config = ctx._apis.projectApi.getConfig()
o.sinon.stub(ctx.project, 'getConfig').resolves({
o.sinon.stub(ctx._apis.projectApi, 'getConfig').returns({
...config,
env: {
...config.env,
NO_COMMAND_LOG: 1,
},
})
hideCommandLog: true,
} as ReceivedCypressOptions)
})
cy.visitApp()
@@ -415,7 +413,7 @@ describe('Cypress in Cypress', { viewportWidth: 1500, defaultCommandTimeout: 100
cy.withCtx(async (ctx) => {
const currentProject = ctx.currentProject?.replaceAll('\\', '/')
const specPath = `${currentProject}/cypress/e2e/dom-content.spec.js`
const url = `http://127.0.0.1:${ctx.gqlServerPort}/__launchpad/graphql?`
const url = `http://127.0.0.1:${ctx.coreData.servers.gqlServerPort}/__launchpad/graphql?`
const payload = `{"query":"mutation{\\nrunSpec(specPath:\\"${specPath}\\"){\\n__typename\\n... on RunSpecResponse{\\ntestingType\\nbrowser{\\nid\\nname\\n}\\nspec{\\nid\\nname\\n}\\n}\\n}\\n}","variables":null}`
ctx.coreData.app.browserStatus = 'open'
@@ -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', '28')
cy.get('[data-selected-spec="false"]').should('have.length', '30')
})
// TODO: Reenable as part of https://github.com/cypress-io/cypress/issues/23902
File diff suppressed because it is too large Load Diff
@@ -72,6 +72,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'fail',
@@ -199,6 +217,46 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'hookName': 'before all',
'err': {
'message': '[error message]',
'name': 'AssertionError',
'stack': 'match.string',
'parsedStack': 'match.array',
},
'state': 'failed',
'pending': false,
'failedFromHookId': 'h1',
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'before all': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -312,6 +370,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'fail',
@@ -439,6 +515,46 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'hookName': 'before each',
'err': {
'message': '[error message]',
'name': 'AssertionError',
'stack': 'match.string',
'parsedStack': 'match.array',
},
'state': 'failed',
'pending': false,
'failedFromHookId': 'h1',
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'before each': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -534,6 +650,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'hook',
@@ -687,6 +821,50 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'hookName': 'after each',
'err': {
'message': '[error message]',
'name': 'AssertionError',
'stack': 'match.string',
'parsedStack': 'match.array',
},
'state': 'failed',
'pending': false,
'failedFromHookId': 'h1',
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -782,6 +960,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'pass',
@@ -838,6 +1034,34 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'timings': {
'lifecycle': 'match.number',
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'test:after:run',
@@ -867,6 +1091,35 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'test',
@@ -902,6 +1155,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r4',
'order': 2,
'title': 'test 2',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'hook',
@@ -1055,6 +1326,50 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r4',
'order': 2,
'title': 'test 2',
'hookName': 'after all',
'err': {
'message': '[error message]',
'name': 'AssertionError',
'stack': 'match.string',
'parsedStack': 'match.array',
},
'state': 'failed',
'pending': false,
'failedFromHookId': 'h1',
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after all': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -1151,6 +1466,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 2',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'hook end',
@@ -1541,6 +1874,69 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 2',
'err': {
'message': '[error message]',
'name': 'AssertionError',
'stack': 'match.string',
'parsedStack': 'match.array',
},
'state': 'failed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'before all': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'before each': [
{
'hookId': 'h2',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h4',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'after all': [
{
'hookId': 'h3',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -1637,6 +2033,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 2',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'hook end',
@@ -2004,6 +2418,63 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 2',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'before all': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'before each': [
{
'hookId': 'h2',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h4',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'after all': [
{
'hookId': 'h3',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -2099,6 +2570,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'pass',
@@ -2199,6 +2688,35 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -2295,6 +2813,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'hook end',
@@ -2532,6 +3068,55 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'timings': {
'lifecycle': 'match.number',
'before all': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'before each': [
{
'hookId': 'h2',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h4',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'test:after:run',
@@ -2582,6 +3167,56 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r3',
'order': 1,
'title': 'test 1',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'before all': [
{
'hookId': 'h1',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'before each': [
{
'hookId': 'h2',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h4',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'test',
@@ -2636,6 +3271,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r4',
'order': 2,
'title': 'test 2',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'hook end',
@@ -2777,6 +3430,48 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:after:run:async',
{
'id': 'r4',
'order': 2,
'title': 'test 2',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'timings': {
'lifecycle': 'match.number',
'before each': [
{
'hookId': 'h2',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h4',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'test:after:run',
@@ -2820,6 +3515,49 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r4',
'order': 2,
'title': 'test 2',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'before each': [
{
'hookId': 'h2',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h4',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'test',
@@ -2874,6 +3612,24 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:before:run:async',
{
'id': 'r5',
'order': 3,
'title': 'test 3',
'pending': false,
'body': '[body]',
'type': 'test',
'wallClockStartedAt': 'match.date',
'file': null,
'invocationDetails': '{Object 9}',
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'hook end',
@@ -3131,6 +3887,56 @@ export const snapshots = {
'_slow': 10000,
},
],
[
'mocha',
'test:after:run:async',
{
'id': 'r5',
'order': 3,
'title': 'test 3',
'state': 'passed',
'pending': false,
'body': '[body]',
'type': 'test',
'duration': 'match.number',
'wallClockStartedAt': 'match.date',
'wallClockDuration': 'match.number',
'timings': {
'lifecycle': 'match.number',
'before each': [
{
'hookId': 'h2',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'test': {
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
'after each': [
{
'hookId': 'h4',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
'after all': [
{
'hookId': 'h3',
'fnDuration': 'match.number',
'afterFnDuration': 'match.number',
},
],
},
'file': null,
'invocationDetails': '{Object 9}',
'final': true,
'currentRetry': 0,
'retries': 0,
'_slow': 10000,
},
],
[
'mocha',
'suite end',
@@ -33,6 +33,7 @@ const eventCleanseMap = {
tests: stringifyShort,
commands: stringifyShort,
invocationDetails: stringifyShort,
hooks: stringifyShort,
body: () => '[body]',
wallClockStartedAt: () => 'match.date',
lifecycle: () => 'match.number',
+5 -4
View File
@@ -1,4 +1,5 @@
import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
import type { SinonStub } from 'sinon'
function moveToRunsPage (): void {
@@ -621,26 +622,26 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
it('displays a copy button and copies correct command in Component Testing', () => {
scaffoldTestingTypeAndVisitRunsPage('component')
cy.withCtx(async (ctx, o) => {
o.sinon.stub(ctx.electronApi, 'copyTextToClipboard')
o.sinon.stub(ctx.config.electronApi, 'copyTextToClipboard')
})
cy.get('[data-cy="copy-button"]').click()
cy.contains('Copied!')
cy.withRetryableCtx((ctx) => {
expect(ctx.electronApi.copyTextToClipboard as SinonStub).to.have.been.calledWith('npx cypress run --component --record --key 2aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
expect(ctx.config.electronApi.copyTextToClipboard as SinonStub).to.have.been.calledWith('npx cypress run --component --record --key 2aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
})
})
it('displays a copy button and copies correct command in E2E', () => {
scaffoldTestingTypeAndVisitRunsPage('e2e')
cy.withCtx(async (ctx, o) => {
o.sinon.stub(ctx.electronApi, 'copyTextToClipboard')
o.sinon.stub(ctx.config.electronApi, 'copyTextToClipboard')
})
cy.get('[data-cy="copy-button"]').click()
cy.contains('Copied!')
cy.withRetryableCtx((ctx) => {
expect(ctx.electronApi.copyTextToClipboard as SinonStub).to.have.been.calledWith('npx cypress run --record --key 2aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
expect(ctx.config.electronApi.copyTextToClipboard as SinonStub).to.have.been.calledWith('npx cypress run --record --key 2aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
})
})
})
+2 -2
View File
@@ -28,7 +28,7 @@ describe('App: Settings', () => {
describe('Cloud Settings', () => {
it('shows the projectId section when there is a projectId and shows override from CLI', () => {
cy.withCtx(async (ctx, o) => {
o.sinon.stub(ctx.electronApi, 'copyTextToClipboard')
o.sinon.stub(ctx.config.electronApi, 'copyTextToClipboard')
})
cy.startAppServer('e2e')
@@ -40,7 +40,7 @@ describe('App: Settings', () => {
cy.findByText('Copy').click()
cy.findByText('Copied!').should('be.visible')
cy.withRetryableCtx((ctx) => {
expect(ctx.electronApi.copyTextToClipboard as SinonStub).to.have.been.calledWith('fromCli')
expect(ctx.config.electronApi.copyTextToClipboard as SinonStub).to.have.been.calledWith('fromCli')
})
})
@@ -152,7 +152,7 @@ describe('App: Spec List (E2E)', () => {
it('displays only matching spec', function () {
cy.get('button')
.contains('24 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 24 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 24 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('24 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('24 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('24 matches')
cy.get('button').contains('26 matches')
})
it('normalizes directory path separators for Windows', function () {
@@ -1,4 +1,5 @@
import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
import type { SinonStub } from 'sinon'
describe('CreateCloudOrgModalSubscription', { viewportWidth: 1200 }, () => {
@@ -51,7 +52,7 @@ describe('CreateCloudOrgModalSubscription', { viewportWidth: 1200 }, () => {
})
cy.withCtx(async (ctx) => {
await ctx.util.fetch(`http://127.0.0.1:${ctx.gqlServerPort}/cloud-notification?operationName=orgCreated`)
await ctx.util.fetch(`http://127.0.0.1:${ctx.coreData.servers.gqlServerPort}/cloud-notification?operationName=orgCreated`)
})
cy.findByText(defaultMessages.runs.connect.modal.selectProject.manageOrgs)
@@ -75,6 +75,8 @@ describe('specChange subscription', () => {
getPathForPlatform('cypress/e2e/accounts/accounts_new.spec.js'),
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'),
@@ -118,6 +120,8 @@ describe('specChange subscription', () => {
getPathForPlatform('cypress/e2e/accounts/accounts_new.spec.js'),
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'),
@@ -188,7 +192,7 @@ e2e: {
cy.get('body').type('f')
cy.get('[data-cy="spec-file-item"]')
.should('have.length', 24)
.should('have.length', 26)
.should('contain', 'blank-contents.spec.js')
.should('contain', 'dom-container.spec.js')
.should('contain', 'dom-content.spec.js')
@@ -199,7 +203,7 @@ e2e: {
}, { path: getPathForPlatform('cypress/e2e/new-file.spec.js') })
cy.get('[data-cy="spec-file-item"]')
.should('have.length', 25)
.should('have.length', 27)
.should('contain', 'blank-contents.spec.js')
.should('contain', 'dom-container.spec.js')
.should('contain', 'dom-content.spec.js')
@@ -214,7 +218,7 @@ e2e: {
cy.get('body').type('f')
cy.get('[data-cy="spec-file-item"]')
.should('have.length', 24)
.should('have.length', 26)
.should('contain', 'blank-contents.spec.js')
.should('contain', 'dom-container.spec.js')
.should('contain', 'dom-content.spec.js')
@@ -225,7 +229,7 @@ e2e: {
}, { path: getPathForPlatform('cypress/e2e/dom-list.spec.js') })
cy.get('[data-cy="spec-file-item"]')
.should('have.length', 23)
.should('have.length', 25)
.should('contain', 'blank-contents.spec.js')
.should('contain', 'dom-container.spec.js')
.should('contain', 'dom-content.spec.js')
@@ -255,6 +259,8 @@ e2e: {
getPathForPlatform('cypress/e2e/accounts/accounts_new.spec.js'),
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'),
@@ -286,7 +292,7 @@ e2e: {
cy.get('body').type('f')
cy.get('[data-cy="spec-file-item"]')
.should('have.length', 24)
.should('have.length', 26)
.should('contain', 'blank-contents.spec.js')
.should('contain', 'dom-container.spec.js')
.should('contain', 'dom-content.spec.js')
@@ -329,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', '24 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', '25 matches')
.should('contain', '27 matches')
})
it('responds to specChange event for a removed file', () => {
@@ -346,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', '24 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', '23 matches')
.should('contain', '25 matches')
})
it('handles removing the last file', () => {
@@ -380,6 +386,8 @@ e2e: {
getPathForPlatform('cypress/e2e/accounts/accounts_new.spec.js'),
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'),
@@ -410,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', '24 matches')
.should('contain', '26 matches')
cy.withCtx(async (ctx) => {
await ctx.actions.file.writeFileInProject('cypress.config.js',
@@ -0,0 +1,12 @@
describe('Test Isolation', () => {
it('fires events in the right order with the right arguments - open mode', () => {
cy.scaffoldProject('cypress-in-cypress')
cy.findBrowsers()
cy.openProject('cypress-in-cypress')
cy.startAppServer()
cy.visitApp(`/specs/runner?file=cypress/e2e/test-isolation.spec.js`)
cy.get('.passed > .num').should('contain', 3)
})
})
@@ -64,6 +64,8 @@
"totalPending": 0,
"totalSkipped": 0,
"totalRunning": 0,
"hasReplay": true,
"replayUrl": "https://cloud.cypress.io/projects/vgqrwp/runs/136/overview/aeb56dee-6dae-4858-b1c2-16ed34088dbb/replay",
"hasStdout": true,
"stdoutUrl": "https://cloud.cypress.io/projects/vgqrwp/runs/136/overview/aeb56dee-6dae-4858-b1c2-16ed34088dbb/stdout",
"hasScreenshots": false,
+2 -2
View File
@@ -1,6 +1,6 @@
/// <reference path="../driver/types/internal-types-lite.d.ts" />
import type { Socket } from '@packages/socket/lib/browser'
import type { SocketShape } from '@packages/socket/lib/types'
import type MobX from 'mobx'
import type { EventManager } from './src/runner/event-manager'
@@ -21,7 +21,7 @@ export {}
*/
declare global {
interface Window {
ws?: Socket
ws?: SocketShape
getEventManager: () => EventManager
UnifiedRunner: {
/**
+4 -4
View File
@@ -14,15 +14,15 @@
"cypress:run:ct": "yarn cypress:run-cypress-in-cypress node ../../scripts/cypress run --component --project .",
"cypress:run:e2e": "yarn cypress:run-cypress-in-cypress node ../../scripts/cypress run --project .",
"dev": "yarn cypress:run-cypress-in-cypress gulp dev --project .",
"start": "echo \"run 'yarn dev' from the root\" && exit 1",
"watch": "echo \"run 'yarn dev' from the root\" && exit 1",
"start": "echo \"run 'yarn dev' or 'yarn watch' from the root\" && exit 1",
"watch": "echo \"run 'yarn dev' or 'yarn watch' from the root\" && exit 1",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, ."
},
"dependencies": {},
"devDependencies": {
"@cypress-design/vue-button": "^0.10.1",
"@cypress-design/vue-icon": "^0.23.1",
"@cypress-design/vue-statusicon": "^0.4.7",
"@cypress-design/vue-icon": "^0.24.3",
"@cypress-design/vue-statusicon": "^0.4.11",
"@cypress-design/vue-tabs": "^0.5.1",
"@graphql-typed-document-node/core": "^3.1.0",
"@headlessui/vue": "1.4.0",
@@ -6,6 +6,7 @@ describe('<DebugArtifacts />', () => {
{ icon: 'TERMINAL_LOG', text: 'View Log', url: 'www.cypress.io' },
{ icon: 'IMAGE_SCREENSHOT', text: 'View Screenshot', url: 'cloud.cypress.io' },
{ icon: 'PLAY', text: 'View Video', url: 'www.cypress.io' },
{ icon: 'REPLAY', text: 'View Replay', url: 'www.cypress.io' },
]
it('mounts correctly, provides expected tooltip content, and emits correct event', () => {
@@ -30,12 +31,14 @@ describe('<DebugArtifacts />', () => {
<DebugArtifactLink icon={'TERMINAL_LOG'} popperText={'View Log'} url={'www.cypress.io'}/>
<DebugArtifactLink icon={'IMAGE_SCREENSHOT'} popperText={'View Screenshot'} url={'cloud.cypress.io'}/>
<DebugArtifactLink icon={'PLAY'} popperText={'View Video'} url={'www.cypress.io'}/>
<DebugArtifactLink icon={'REPLAY'} popperText={'View Test Replay'} url={'www.cypress.io'}/>
</div>
))
cy.findByTestId('debug-artifacts-all').children().should('have.length', 3)
cy.findByTestId('debug-artifacts-all').children().should('have.length', 4)
cy.findByTestId(`TERMINAL_LOG-button`).should('have.attr', 'href', 'www.cypress.io')
cy.findByTestId(`IMAGE_SCREENSHOT-button`).should('have.attr', 'href', 'cloud.cypress.io')
cy.findByTestId(`PLAY-button`).should('have.attr', 'href', 'www.cypress.io')
cy.findByTestId(`REPLAY-button`).should('have.attr', 'href', 'www.cypress.io')
})
})
+2 -1
View File
@@ -31,7 +31,7 @@
</Tooltip>
</template>
<script lang="ts" setup>
import { IconTechnologyTerminalLog, IconTechnologyImageScreenshot, IconActionPlaySmall } from '@cypress-design/vue-icon'
import { IconTechnologyTerminalLog, IconTechnologyImageScreenshot, IconActionPlaySmall, IconActionTestReplay } from '@cypress-design/vue-icon'
import Tooltip from '@packages/frontend-shared/src/components/Tooltip.vue'
import ExternalLink from '@cy/gql-components/ExternalLink.vue'
import type { ArtifactType } from './utils/debugArtifacts'
@@ -46,6 +46,7 @@ const ICON_MAP: Record<ArtifactType, any> = {
'TERMINAL_LOG': IconTechnologyTerminalLog,
'IMAGE_SCREENSHOT': IconTechnologyImageScreenshot,
'PLAY': IconActionPlaySmall,
'REPLAY': IconActionTestReplay,
}
</script>
+21 -2
View File
@@ -36,6 +36,8 @@ const instance1: TestResults['instance'] = {
groupId: '123',
status: 'FAILED',
hasScreenshots: true,
hasReplay: true,
replayUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/replay',
screenshotsUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/screenshots',
hasStdout: true,
stdoutUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/stdout',
@@ -116,7 +118,7 @@ describe('<DebugFailedTest/>', () => {
assertRowContents(testResult)
cy.findByTestId('test-group').realHover()
cy.findByTestId('debug-artifacts').should('be.visible').children().should('have.length', 3)
cy.findByTestId('debug-artifacts').should('be.visible').children().should('have.length', 4)
cy.findByTestId('debug-artifacts').children().each((artifact) => {
cy.wrap(artifact).find('a').should('have.attr', 'href')
.and('match', /utm_medium/)
@@ -163,7 +165,7 @@ describe('<DebugFailedTest/>', () => {
cy.findByTestId('debug-artifacts').should('not.exist')
cy.findAllByTestId('grouped-row').should('have.length', 2)
cy.findAllByTestId('grouped-row').first().realHover()
cy.findAllByTestId('debug-artifacts').first().should('be.visible').children().should('have.length', 3)
cy.findAllByTestId('debug-artifacts').first().should('be.visible').children().should('have.length', 4)
cy.percySnapshot()
})
@@ -205,6 +207,7 @@ describe('<DebugFailedTest/>', () => {
hasStdout: false,
hasScreenshots: false,
hasVideo: false,
hasReplay: false,
}
render({ ...testResult, instance: artifactFreeInstance })
@@ -221,5 +224,21 @@ describe('<DebugFailedTest/>', () => {
render({ ...testResult, instance: { ...artifactFreeInstance, hasVideo: true } })
cy.findByTestId('debug-artifacts').children().should('have.length', 1)
cy.findByTestId('PLAY-button').should('exist')
render({ ...testResult, instance: { ...artifactFreeInstance, hasReplay: true } })
cy.findByTestId('debug-artifacts').children().should('have.length', 1)
cy.findByTestId('REPLAY-button').should('exist')
render({ ...testResult, instance: instance1 })
cy.findByTestId('debug-artifacts').children()
.should('have.length', 4)
.first()
.should('have.attr', 'data-cy', 'artifact--REPLAY')
.next()
.should('have.attr', 'data-cy', 'artifact--TERMINAL_LOG')
.next()
.should('have.attr', 'data-cy', 'artifact--IMAGE_SCREENSHOT')
.next()
.should('have.attr', 'data-cy', 'artifact--PLAY')
})
})
+14
View File
@@ -59,9 +59,11 @@ const testResultMultipleGroups: {[thumbprint: string]: TestResults[]} = {
id: '123',
status: 'FAILED',
groupId: '123',
hasReplay: true,
hasScreenshots: true,
hasStdout: true,
hasVideo: true,
replayUrl: 'www.cypress.io',
screenshotsUrl: 'www.cypress.io',
stdoutUrl: 'www.cypress.io',
videoUrl: 'www.cypress.io',
@@ -74,9 +76,11 @@ const testResultMultipleGroups: {[thumbprint: string]: TestResults[]} = {
id: '456',
status: 'FAILED',
groupId: '456',
hasReplay: true,
hasScreenshots: true,
hasStdout: true,
hasVideo: true,
replayUrl: 'www.cypress.io',
screenshotsUrl: 'www.cypress.io',
stdoutUrl: 'www.cypress.io',
videoUrl: 'www.cypress.io',
@@ -91,9 +95,11 @@ const testResultMultipleGroups: {[thumbprint: string]: TestResults[]} = {
id: '123',
status: 'FAILED',
groupId: '123',
hasReplay: true,
hasScreenshots: true,
hasStdout: true,
hasVideo: true,
replayUrl: 'www.cypress.io',
screenshotsUrl: 'www.cypress.io',
stdoutUrl: 'www.cypress.io',
videoUrl: 'www.cypress.io',
@@ -126,9 +132,11 @@ const testResultSingleGroup: {[thumbprint: string]: TestResults[]} = {
id: '123',
status: 'FAILED',
groupId: '123',
hasReplay: true,
hasScreenshots: true,
hasStdout: true,
hasVideo: true,
replayUrl: 'www.cypress.io',
screenshotsUrl: 'www.cypress.io',
stdoutUrl: 'www.cypress.io',
videoUrl: 'www.cypress.io',
@@ -143,9 +151,11 @@ const testResultSingleGroup: {[thumbprint: string]: TestResults[]} = {
id: '123',
status: 'FAILED',
groupId: '123',
hasReplay: true,
hasScreenshots: true,
hasStdout: true,
hasVideo: true,
replayUrl: 'www.cypress.io',
screenshotsUrl: 'www.cypress.io',
stdoutUrl: 'www.cypress.io',
videoUrl: 'www.cypress.io',
@@ -269,9 +279,11 @@ describe('<DebugSpec/> responsive UI', () => {
id: '123',
status: 'FAILED',
groupId: '123',
hasReplay: true,
hasScreenshots: true,
hasStdout: true,
hasVideo: true,
replayUrl: 'www.cypress.io',
screenshotsUrl: 'www.cypress.io',
stdoutUrl: 'www.cypress.io',
videoUrl: 'www.cypress.io',
@@ -286,9 +298,11 @@ describe('<DebugSpec/> responsive UI', () => {
id: '123',
status: 'FAILED',
groupId: '123',
hasReplay: true,
hasScreenshots: true,
hasStdout: true,
hasVideo: true,
replayUrl: 'www.cypress.io',
screenshotsUrl: 'www.cypress.io',
stdoutUrl: 'www.cypress.io',
videoUrl: 'www.cypress.io',
@@ -37,9 +37,11 @@ const specs: CloudDebugSpec[] = [{
instance: {
id: '123',
groupId: '',
hasReplay: false,
hasScreenshots: false,
hasStdout: false,
hasVideo: false,
replayUrl: '',
status: 'FAILED',
screenshotsUrl: '',
stdoutUrl: '',
+2
View File
@@ -82,6 +82,8 @@ fragment DebugSpecListTests on CloudTestResult {
screenshotsUrl
hasVideo
videoUrl
hasReplay
replayUrl
}
}
`
@@ -16,6 +16,8 @@ describe('<GroupedDebugFailedTest/>', () => {
stdoutUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/stdout',
hasVideo: true,
videoUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/video',
hasReplay: true,
replayUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/replay',
},
},
{
@@ -31,6 +33,8 @@ describe('<GroupedDebugFailedTest/>', () => {
stdoutUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/stdout',
hasVideo: true,
videoUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/video',
hasReplay: true,
replayUrl: 'https://cloud.cypress.io/projects/123/runs/456/overview/789/replay',
},
},
]
@@ -82,7 +86,7 @@ describe('<GroupedDebugFailedTest/>', () => {
cy.findAllByTestId(`grouped-row`).should('have.length', 2).each((el) => cy.wrap(el).within(() => {
cy.findByTestId('debug-artifacts').should('not.be.visible')
cy.findByTestId('test-failed-metadata').realHover()
cy.findByTestId('debug-artifacts').should('be.visible').children().should('have.length', 3)
cy.findByTestId('debug-artifacts').should('be.visible').children().should('have.length', 4)
cy.findByTestId('stats-metadata').children().should('have.length', 3)
}))
})
@@ -3,7 +3,7 @@ import { getUrlWithParams } from '@packages/frontend-shared/src/utils/getUrlWith
import type { useI18n } from '@cy/i18n'
import { DEBUG_TAB_MEDIUM } from './constants'
export type ArtifactType = 'TERMINAL_LOG' | 'IMAGE_SCREENSHOT' | 'PLAY'
export type ArtifactType = 'TERMINAL_LOG' | 'IMAGE_SCREENSHOT' | 'PLAY' | 'REPLAY'
export type DebugArtifact = { icon: ArtifactType, text: string, url: string }
@@ -14,6 +14,10 @@ const formatUrl = (url: string, campaign: string): string => {
export const getDebugArtifacts = (instance: CloudRunInstance | null, t: ReturnType<typeof useI18n>['t']): DebugArtifact[] => {
const debugArtifacts: DebugArtifact[] = []
if (instance?.hasReplay && instance.replayUrl) {
debugArtifacts.push({ icon: 'REPLAY', text: t('debugPage.artifacts.replay'), url: formatUrl(instance.replayUrl, 'Test Replay') })
}
if (instance?.hasStdout && instance.stdoutUrl) {
debugArtifacts.push({ icon: 'TERMINAL_LOG', text: t('debugPage.artifacts.stdout'), url: formatUrl(instance.stdoutUrl, 'Output') })
}
@@ -233,7 +233,7 @@ if (!hideCommandLog) {
preferences.update('reporterWidth', reporterWidthPreferences.value)
preferences.update('specListWidth', specsListWidthPreferences.value)
// 👆 we must update these preferences before calling useRunnerStyle, to make sure that values from GQL
// will be available during the initial calculation that useRunnerStyle does
// will be available during the initial calculation that useRunnerStyle does
}
const {
@@ -36,13 +36,14 @@
<template #panel3>
<HideDuringScreenshot>
<SpecRunnerHeaderRunMode
v-if="!hideRunnerUi"
:event-manager="eventManager"
:get-aut-iframe="getAutIframeModel"
class="bg-white"
/>
</HideDuringScreenshot>
<RemoveClassesDuringScreenshotting
class="h-0 p-[16px]"
:class="(hideRunnerUi) ? '' : 'h-0 p-[16px]'"
>
<ScriptError
v-if="autStore.scriptError"
@@ -108,6 +109,7 @@ const {
} = useEventManager()
const hideCommandLog = runnerUiStore.hideCommandLog
const hideRunnerUi = runnerUiStore.hideRunnerUi
// watch active spec, and re-run if it changes!
startSpecWatcher()
+11 -5
View File
@@ -470,15 +470,21 @@ export class AutIframe {
if (!$el) {
return logger.logFormatted({
Command: selectorPlaygroundStore.command,
Yielded: 'Nothing',
name: selectorPlaygroundStore.command,
type: 'command',
props: {
Yielded: 'Nothing',
},
})
}
logger.logFormatted({
Command: selectorPlaygroundStore.command,
Elements: $el.length,
Yielded: Cypress.dom.getElements($el),
name: selectorPlaygroundStore.command,
type: 'command',
props: {
Elements: $el.length,
Yielded: Cypress.dom.getElements($el),
},
})
}
+29 -24
View File
@@ -7,7 +7,7 @@ import type { LocalBusEmitsMap, LocalBusEventMap, DriverToLocalBus, SocketToDriv
import type { RunState, CachedTestState, AutomationElementId, FileDetails, ReporterStartInfo, ReporterRunState } from '@packages/types'
import { logger } from './logger'
import type { Socket } from '@packages/socket/lib/browser'
import type { SocketShape } from '@packages/socket/lib/types'
import { automation, useRunnerUiStore, useSpecStore } from '../store'
import { useScreenshotStore } from '../store/screenshot-store'
import { useStudioStore } from '../store/studio-store'
@@ -15,6 +15,7 @@ import { getAutIframeModel } from '.'
import { handlePausing } from './events/pausing'
import { addTelemetryListeners } from './events/telemetry'
import { telemetry } from '@packages/telemetry/src/browser'
import { addCaptureProtocolListeners } from './events/capture-protocol'
export type CypressInCypressMochaEvent = Array<Array<string | Record<string, any>>>
@@ -56,7 +57,7 @@ export class EventManager {
selectorPlaygroundModel: any
cypressInCypressMochaEvents: CypressInCypressMochaEvent[] = []
// Used for testing the experimentalSingleTabRunMode experiment. Ensures AUT is correctly destroyed between specs.
ws: Socket
ws: SocketShape
specStore: ReturnType<typeof useSpecStore>
studioStore: ReturnType<typeof useStudioStore>
@@ -67,7 +68,7 @@ export class EventManager {
private Mobx: typeof MobX,
// selectorPlaygroundModel singleton
selectorPlaygroundModel: any,
ws: Socket,
ws: SocketShape,
) {
this.selectorPlaygroundModel = selectorPlaygroundModel
this.ws = ws
@@ -133,7 +134,7 @@ export class EventManager {
telemetry.setRootContext(context)
})
this.ws.on('automation:push:message', (msg, data = {}) => {
this.ws.on('automation:push:message', (msg, data: any = {}) => {
if (!Cypress) return
switch (msg) {
@@ -412,7 +413,7 @@ export class EventManager {
return
}
const hideCommandLog = window.__CYPRESS_CONFIG__.hideCommandLog
const hideCommandLog = Cypress.config('hideCommandLog')
this.studioStore.initialize(config, runState)
@@ -459,6 +460,10 @@ export class EventManager {
_addListeners () {
addTelemetryListeners(Cypress)
if (Cypress.config('protocolEnabled')) {
addCaptureProtocolListeners(Cypress)
}
Cypress.on('message', (msg, data, cb) => {
this.ws.emit('client:request', msg, data, cb)
})
@@ -470,7 +475,7 @@ export class EventManager {
})
Cypress.on('collect:run:state', () => {
if (Cypress.env('NO_COMMAND_LOG')) {
if (Cypress.config('hideCommandLog')) {
return Bluebird.resolve()
}
@@ -518,7 +523,7 @@ export class EventManager {
const screenshotStore = useScreenshotStore()
const handleBeforeScreenshot = (config, cb) => {
if (config.appOnly) {
if (config.appOnly || Cypress.config('hideRunnerUi')) {
screenshotStore.setScreenshotting(true)
}
@@ -527,7 +532,7 @@ export class EventManager {
cb()
}
if (Cypress.env('NO_COMMAND_LOG')) {
if (Cypress.config('hideCommandLog')) {
return beforeThenCb()
}
@@ -558,14 +563,6 @@ export class EventManager {
})
})
Cypress.on('test:before:run:async', (test, _runnable) => {
this.reporterBus.emit('test:before:run:async', test)
})
Cypress.on('test:after:run', (test, _runnable) => {
this.reporterBus.emit('test:after:run', test, Cypress.config('isInteractive'))
})
Cypress.on('run:start', async () => {
if (Cypress.config('experimentalMemoryManagement') && Cypress.isBrowser({ family: 'chromium' })) {
await Cypress.backend('start:memory:profiling', Cypress.config('spec'))
@@ -607,20 +604,32 @@ export class EventManager {
this.localBus.emit('script:error', err)
})
Cypress.on('test:before:run:async', async (_attr, test) => {
Cypress.on('test:before:run:async', async (...args) => {
const [attributes, test] = args
this.reporterBus.emit('test:before:run:async', attributes)
this.studioStore.interceptTest(test)
// if the experimental flag is on and we are in a chromium based browser,
// check the memory pressure to determine if garbage collection is needed
if (Cypress.config('experimentalMemoryManagement') && Cypress.isBrowser({ family: 'chromium' })) {
await Cypress.backend('check:memory:pressure', {
test: { title: test.title, order: test.order, currentRetry: test.currentRetry() },
test: { title: attributes.title, order: attributes.order, currentRetry: attributes.currentRetry },
})
}
Cypress.primaryOriginCommunicator.toAllSpecBridges('test:before:run:async', ...args)
})
Cypress.on('test:after:run', (test) => {
if (this.studioStore.isOpen && test.state !== 'passed') {
Cypress.on('test:before:after:run:async', (...args) => {
Cypress.primaryOriginCommunicator.toAllSpecBridges('test:before:after:run:async', ...args)
})
Cypress.on('test:after:run', (attributes) => {
this.reporterBus.emit('test:after:run', attributes, Cypress.config('isInteractive'))
if (this.studioStore.isOpen && attributes.state !== 'passed') {
this.studioStore.testFailed()
}
})
@@ -631,10 +640,6 @@ export class EventManager {
Cypress.primaryOriginCommunicator.toAllSpecBridges('test:before:run', ...args)
})
Cypress.on('test:before:run:async', (...args) => {
Cypress.primaryOriginCommunicator.toAllSpecBridges('test:before:run:async', ...args)
})
// Inform all spec bridges that the primary origin has begun to unload.
Cypress.on('window:before:unload', () => {
Cypress.primaryOriginCommunicator.toAllSpecBridges('before:unload', window.origin)
@@ -0,0 +1,124 @@
const attachCypressProtocolInfo = (info) => {
let cypressProtocolElement: HTMLElement | null = document.getElementById('__cypress-protocol')
// If element does not exist, create it
if (!cypressProtocolElement) {
cypressProtocolElement = document.createElement('div')
cypressProtocolElement.id = '__cypress-protocol'
cypressProtocolElement.style.display = 'none'
document.body.appendChild(cypressProtocolElement)
}
cypressProtocolElement.dataset.cypressProtocolInfo = JSON.stringify(info)
}
export const addCaptureProtocolListeners = (Cypress: Cypress.Cypress) => {
Cypress.on('cy:protocol-snapshot', () => {
attachCypressProtocolInfo({
type: 'cy:protocol-snapshot',
timestamp: performance.now() + performance.timeOrigin,
})
})
Cypress.on('log:added', (attributes) => {
// TODO: UNIFY-1318 - Race condition in unified runner - we should not need this null check
if (!Cypress.runner) {
return
}
const protocolProps = Cypress.runner.getProtocolPropsForLog(attributes)
attachCypressProtocolInfo({
type: 'log:added',
timestamp: performance.now() + performance.timeOrigin,
})
Cypress.backend('protocol:command:log:added', protocolProps)
})
Cypress.on('log:changed', (attributes) => {
// TODO: UNIFY-1318 - Race condition in unified runner - we should not need this null check
if (!Cypress.runner) {
return
}
const protocolProps = Cypress.runner.getProtocolPropsForLog(attributes)
attachCypressProtocolInfo({
type: 'log:changed',
timestamp: performance.now() + performance.timeOrigin,
})
Cypress.backend('protocol:command:log:changed', protocolProps)
})
const viewportChangedHandler = (viewport) => {
const timestamp = performance.timeOrigin + performance.now()
attachCypressProtocolInfo({
type: 'viewport:changed',
timestamp,
})
Cypress.backend('protocol:viewport:changed', {
viewport: {
width: viewport.viewportWidth,
height: viewport.viewportHeight,
},
timestamp,
})
}
Cypress.on('viewport:changed', viewportChangedHandler)
// @ts-expect-error
Cypress.primaryOriginCommunicator.on('viewport:changed', viewportChangedHandler)
Cypress.on('test:before:run:async', async (attributes) => {
attachCypressProtocolInfo({
type: 'test:before:run:async',
timestamp: performance.now() + performance.timeOrigin,
})
await Cypress.backend('protocol:test:before:run:async', attributes)
})
Cypress.on('url:changed', (url) => {
const timestamp = performance.timeOrigin + performance.now()
attachCypressProtocolInfo({
type: 'url:changed',
timestamp,
})
Cypress.backend('protocol:url:changed', { url, timestamp })
})
Cypress.on('page:loading', (loading) => {
const timestamp = performance.timeOrigin + performance.now()
attachCypressProtocolInfo({
type: 'page:loading',
timestamp,
})
Cypress.backend('protocol:page:loading', { loading, timestamp })
})
Cypress.on('test:before:after:run:async', async (attributes, _test, options) => {
attachCypressProtocolInfo({
type: 'test:before:after:run:async',
timestamp: performance.timeOrigin + performance.now(),
})
await Cypress.backend('protocol:test:before:after:run:async', attributes, options)
})
Cypress.on('test:after:run:async', async (attributes) => {
attachCypressProtocolInfo({
type: 'test:after:run:async',
timestamp: performance.timeOrigin + performance.now(),
})
await Cypress.backend('protocol:test:after:run:async', attributes)
})
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { telemetry } from '@packages/telemetry/src/browser'
export const addTelemetryListeners = (Cypress) => {
export const addTelemetryListeners = (Cypress: Cypress.Cypress) => {
Cypress.on('test:before:run', (attributes, test) => {
// we emit the 'test:before:run' events within various driver tests
try {
+5 -1
View File
@@ -248,9 +248,13 @@ function runSpecCT (config, spec: SpecFile) {
const autIframe = getAutIframeModel()
const $autIframe: JQuery<HTMLIFrameElement> = autIframe.create().appendTo($container)
// the iframe controller will forward the specpath via header to the devserver.
// using a query parameter allows us to recognize relative requests and proxy them to the devserver.
const specIndexUrl = `index.html?specPath=${encodeURI(spec.absolute)}`
const specSrc = getSpecUrl({
namespace: config.namespace,
specSrc: spec.absolute,
specSrc: specIndexUrl,
})
autIframe._showInitialBlankPage()
+9 -1
View File
@@ -38,7 +38,13 @@ export const logger = {
},
_logValues (consoleProps: any) {
const formattedLog = this._formatted(_.omit(consoleProps, 'args', 'groups', 'table'))
consoleProps ||= {}
const formattedLog = this._formatted({
[consoleProps.type]: consoleProps.name,
..._.pick(consoleProps, 'error', 'snapshot'),
...consoleProps.props,
})
_.each(formattedLog, (value, key) => {
// don't log empty strings
@@ -56,6 +62,8 @@ export const logger = {
const maxKeyLength = this._getMaxKeyLength(consoleProps)
return _.reduce(consoleProps, (memo, value, key) => {
if (!key || key === 'undefined') return memo
const append = ': '
key = _.capitalize(key + append).padEnd(maxKeyLength + append.length, ' ')
@@ -159,9 +159,12 @@ describe('SelectorPlayground', () => {
*/
cy.then(() => {
expect(logger.logFormatted).to.have.been.calledWith({
Command: `cy.get('.foo-bar')`,
Elements: 2,
Yielded: undefined, // stubbed dom does not actually return anything
name: `cy.get('.foo-bar')`,
type: 'command',
props: {
Elements: 2,
Yielded: undefined, // stubbed dom does not actually return anything
},
})
})
})
@@ -174,8 +177,11 @@ describe('SelectorPlayground', () => {
cy.get('[data-cy="playground-print"]').as('print')
cy.get('@print').click().then(() => {
expect(logger.logFormatted).to.have.been.calledWith({
Command: `cy.get('.foo-bar')`,
Yielded: 'Nothing',
name: `cy.get('.foo-bar')`,
type: 'command',
props: {
Yielded: 'Nothing',
},
})
})
})
+8 -4
View File
@@ -35,15 +35,18 @@ export const useRunnerStyle = () => {
const miscBorders = 4
const containerMinimum = 50
// start with just the margin since all other elements that take up width
// might not be there
let nonAutWidth = autMargin * 2
let nonAutWidth = 0
if (!isRunMode) {
nonAutWidth += collapsedNavBarWidth
}
if (!window.__CYPRESS_CONFIG__.hideCommandLog) {
// if we are not hiding the entire runner, the margins need to be added in
if (!window.__CYPRESS_CONFIG__.hideRunnerUi) {
nonAutWidth += (autMargin * 2)
}
nonAutWidth += reporterWidth.value + specListWidth.value + miscBorders
}
@@ -55,7 +58,8 @@ export const useRunnerStyle = () => {
})
const containerHeight = computed(() => {
const nonAutHeight = autStore.specRunnerHeaderHeight + (autMargin * 2)
// if the entire runner is being hidden, there is not non-aut height, otherwise we need to account for the header and margins
const nonAutHeight = window.__CYPRESS_CONFIG__.hideRunnerUi ? 0 : autStore.specRunnerHeaderHeight + (autMargin * 2)
return windowHeight.value - nonAutHeight
})
@@ -27,6 +27,7 @@ export interface RunnerUiState {
automationStatus: AutomationStatus
randomString: string
hideCommandLog: boolean
hideRunnerUi: boolean
}
export const useRunnerUiStore = defineStore({
@@ -42,6 +43,7 @@ export const useRunnerUiStore = defineStore({
automationStatus: automation.CONNECTING,
randomString: `${Math.random()}`,
hideCommandLog: window.__CYPRESS_CONFIG__.hideCommandLog,
hideRunnerUi: window.__CYPRESS_CONFIG__.hideRunnerUi,
}
},
+11 -10
View File
@@ -11,10 +11,9 @@ exports['config/src/index .getBreakingKeys returns list of breaking config keys
'firefoxGcInterval',
'ignoreTestFiles',
'integrationFolder',
'nodeVersion',
'nodeVersion',
'pluginsFile',
'testFiles',
'videoUploadOnPasses',
]
exports['config/src/index .getDefaultValues returns list of public config keys 1'] = {
@@ -77,10 +76,9 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'testIsolation': true,
'trashAssetsBeforeRuns': true,
'userAgent': null,
'video': true,
'videoCompression': 32,
'video': false,
'videoCompression': false,
'videosFolder': 'cypress/videos',
'videoUploadOnPasses': true,
'viewportHeight': 660,
'viewportWidth': 1000,
'waitForAnimations': true,
@@ -103,6 +101,9 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'socketId': null,
'socketIoCookie': '__socket',
'socketIoRoute': '/__socket',
'protocolEnabled': false,
'hideCommandLog': false,
'hideRunnerUi': false,
}
exports['config/src/index .getDefaultValues returns list of public config keys for selected testing type 1'] = {
@@ -165,10 +166,9 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'testIsolation': true,
'trashAssetsBeforeRuns': true,
'userAgent': null,
'video': true,
'videoCompression': 32,
'video': false,
'videoCompression': false,
'videosFolder': 'cypress/videos',
'videoUploadOnPasses': true,
'viewportHeight': 660,
'viewportWidth': 1000,
'waitForAnimations': true,
@@ -191,6 +191,9 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'socketId': null,
'socketIoCookie': '__socket',
'socketIoRoute': '/__socket',
'protocolEnabled': false,
'hideCommandLog': false,
'hideRunnerUi': false,
}
exports['config/src/index .getPublicConfigKeys returns list of public config keys 1'] = [
@@ -224,7 +227,6 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
'includeShadowDom',
'keystrokeDelay',
'modifyObstructiveCode',
'nodeVersion',
'numTestsKeptInMemory',
'platform',
'pageLoadTimeout',
@@ -251,7 +253,6 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
'video',
'videoCompression',
'videosFolder',
'videoUploadOnPasses',
'viewportHeight',
'viewportWidth',
'waitForAnimations',
+25 -22
View File
@@ -25,9 +25,8 @@ const BREAKING_OPTION_ERROR_KEY: Readonly<AllCypressErrorNames[]> = [
'EXPERIMENTAL_SINGLE_TAB_RUN_MODE',
'EXPERIMENTAL_SHADOW_DOM_REMOVED',
'FIREFOX_GC_INTERVAL_REMOVED',
'NODE_VERSION_DEPRECATION_SYSTEM',
'NODE_VERSION_DEPRECATION_BUNDLED',
'PLUGINS_FILE_CONFIG_OPTION_REMOVED',
'VIDEO_UPLOAD_ON_PASSES_REMOVED',
'RENAMED_CONFIG_OPTION',
'TEST_FILES_RENAMED',
] as const
@@ -300,9 +299,6 @@ const driverConfigOptions: Array<DriverConfigOption> = [
defaultValue: true,
validation: validate.isBoolean,
requireRestartOnChange: 'server',
}, {
name: 'nodeVersion',
validation: validate.isOneOf('bundled', 'system'),
}, {
name: 'numTestsKeptInMemory',
defaultValue: 50,
@@ -429,21 +425,17 @@ const driverConfigOptions: Array<DriverConfigOption> = [
requireRestartOnChange: 'browser',
}, {
name: 'video',
defaultValue: true,
defaultValue: false,
validation: validate.isBoolean,
}, {
name: 'videoCompression',
defaultValue: 32,
defaultValue: false,
validation: validate.isValidCrfOrBoolean,
}, {
name: 'videosFolder',
defaultValue: 'cypress/videos',
validation: validate.isString,
isFolder: true,
}, {
name: 'videoUploadOnPasses',
defaultValue: true,
validation: validate.isBoolean,
}, {
name: 'viewportHeight',
defaultValue: (options: Record<string, any> = {}) => options.testingType === 'component' ? 500 : 660,
@@ -571,6 +563,22 @@ const runtimeOptions: Array<RuntimeConfigOption> = [
defaultValue: pkg.version,
validation: validate.isString,
isInternal: true,
}, {
name: 'protocolEnabled',
defaultValue: false,
validation: validate.isBoolean,
isInternal: true,
}, {
name: 'hideCommandLog',
defaultValue: false,
validation: validate.isBoolean,
isInternal: true,
},
{
name: 'hideRunnerUi',
defaultValue: false,
validation: validate.isBoolean,
isInternal: true,
},
]
@@ -633,25 +641,20 @@ export const breakingOptions: Readonly<BreakingOption[]> = [
name: 'integrationFolder',
errorKey: 'INTEGRATION_FOLDER_REMOVED',
isWarning: false,
}, {
name: 'nodeVersion',
value: 'system',
errorKey: 'NODE_VERSION_DEPRECATION_SYSTEM',
isWarning: true,
}, {
name: 'nodeVersion',
value: 'bundled',
errorKey: 'NODE_VERSION_DEPRECATION_BUNDLED',
isWarning: true,
}, {
name: 'pluginsFile',
errorKey: 'PLUGINS_FILE_CONFIG_OPTION_REMOVED',
isWarning: false,
}, {
},
{
name: 'testFiles',
errorKey: 'TEST_FILES_RENAMED',
newName: 'specPattern',
isWarning: false,
}, {
name: 'videoUploadOnPasses',
errorKey: 'VIDEO_UPLOAD_ON_PASSES_REMOVED',
isWarning: true,
},
] as const
+1 -1
View File
@@ -250,7 +250,7 @@ const convertRelativeToAbsolutePaths = (projectRoot: string, obj: Config) => {
// instead of the built-in Node process, specify a path to 3rd party Node
export const setNodeBinary = (obj: Config, userNodePath?: string, userNodeVersion?: string) => {
// if execPath isn't found we weren't executed from the CLI and should used the bundled node version.
if (userNodePath && userNodeVersion && obj.nodeVersion !== 'bundled') {
if (userNodePath && userNodeVersion) {
obj.resolvedNodePath = userNodePath
obj.resolvedNodeVersion = userNodeVersion
+11 -36
View File
@@ -409,47 +409,30 @@ describe('config/src/project/utils', () => {
this.nodeVersion = process.versions.node
})
it('sets bundled Node ver if nodeVersion != system', function () {
it('sets cli Node ver', function () {
const obj = setNodeBinary({
nodeVersion: 'bundled',
})
expect(obj).to.deep.eq({
nodeVersion: 'bundled',
resolvedNodeVersion: this.nodeVersion,
})
})
it('sets cli Node ver if nodeVersion = system', function () {
const obj = setNodeBinary({
nodeVersion: 'system',
}, '/foo/bar/node', '1.2.3')
expect(obj).to.deep.eq({
nodeVersion: 'system',
resolvedNodeVersion: '1.2.3',
resolvedNodePath: '/foo/bar/node',
})
})
it('sets bundled Node ver and if nodeVersion = system and userNodePath undefined', function () {
it('sets userNodePath undefined', function () {
const obj = setNodeBinary({
nodeVersion: 'system',
}, undefined, '1.2.3')
expect(obj).to.deep.eq({
nodeVersion: 'system',
resolvedNodeVersion: this.nodeVersion,
})
})
it('sets bundled Node ver and if nodeVersion = system and userNodeVersion undefined', function () {
it('sets userNodeVersion undefined', function () {
const obj = setNodeBinary({
nodeVersion: 'system',
}, '/foo/bar/node')
expect(obj).to.deep.eq({
nodeVersion: 'system',
resolvedNodeVersion: this.nodeVersion,
})
})
@@ -779,16 +762,12 @@ describe('config/src/project/utils', () => {
return this.defaults('animationDistanceThreshold', 5)
})
it('video=true', function () {
return this.defaults('video', true)
it('video=false', function () {
return this.defaults('video', false)
})
it('videoCompression=32', function () {
return this.defaults('videoCompression', 32)
})
it('videoUploadOnPasses=true', function () {
return this.defaults('videoUploadOnPasses', true)
it('videoCompression=false', function () {
return this.defaults('videoCompression', false)
})
it('trashAssetsBeforeRuns=32', function () {
@@ -1099,7 +1078,6 @@ describe('config/src/project/utils', () => {
isInteractive: { value: true, from: 'default' },
keystrokeDelay: { value: 0, from: 'default' },
modifyObstructiveCode: { value: true, from: 'default' },
nodeVersion: { value: undefined, from: 'default' },
numTestsKeptInMemory: { value: 50, from: 'default' },
pageLoadTimeout: { value: 60000, from: 'default' },
platform: { value: os.platform(), from: 'default' },
@@ -1123,10 +1101,9 @@ describe('config/src/project/utils', () => {
testIsolation: { value: true, from: 'default' },
trashAssetsBeforeRuns: { value: true, from: 'default' },
userAgent: { value: null, from: 'default' },
video: { value: true, from: 'default' },
videoCompression: { value: 32, from: 'default' },
video: { value: false, from: 'default' },
videoCompression: { value: false, from: 'default' },
videosFolder: { value: 'cypress/videos', from: 'default' },
videoUploadOnPasses: { value: true, from: 'default' },
viewportHeight: { value: 660, from: 'default' },
viewportWidth: { value: 1000, from: 'default' },
waitForAnimations: { value: true, from: 'default' },
@@ -1219,7 +1196,6 @@ describe('config/src/project/utils', () => {
isInteractive: { value: true, from: 'default' },
keystrokeDelay: { value: 0, from: 'default' },
modifyObstructiveCode: { value: true, from: 'default' },
nodeVersion: { value: undefined, from: 'default' },
numTestsKeptInMemory: { value: 50, from: 'default' },
pageLoadTimeout: { value: 60000, from: 'default' },
platform: { value: os.platform(), from: 'default' },
@@ -1243,10 +1219,9 @@ describe('config/src/project/utils', () => {
testIsolation: { value: true, from: 'default' },
trashAssetsBeforeRuns: { value: true, from: 'default' },
userAgent: { value: null, from: 'default' },
video: { value: true, from: 'default' },
videoCompression: { value: 32, from: 'default' },
video: { value: false, from: 'default' },
videoCompression: { value: false, from: 'default' },
videosFolder: { value: 'cypress/videos', from: 'default' },
videoUploadOnPasses: { value: true, from: 'default' },
viewportHeight: { value: 660, from: 'default' },
viewportWidth: { value: 1000, from: 'default' },
waitForAnimations: { value: true, from: 'default' },
+6
View File
@@ -10,6 +10,7 @@ import {
BrowserActions,
DevActions,
AuthActions,
ServersActions,
CohortsActions,
CodegenActions,
CloudProjectActions,
@@ -78,6 +79,11 @@ export class DataActions {
return new BrowserActions(this.ctx)
}
@cached
get servers () {
return new ServersActions(this.ctx)
}
@cached
get versions () {
return new VersionsActions(this.ctx)
+25 -129
View File
@@ -1,7 +1,6 @@
import type { AllModeOptions } from '@packages/types'
import fsExtra from 'fs-extra'
import path from 'path'
import util from 'util'
import chalk from 'chalk'
import assert from 'assert'
import str from 'underscore.string'
@@ -29,20 +28,18 @@ import {
MigrationDataSource,
RelevantRunsDataSource,
RelevantRunSpecsDataSource,
} from './sources/'
VersionsDataSource,
ErrorDataSource,
GraphQLDataSource,
RemoteRequestDataSource,
} from './sources'
import { cached } from './util/cached'
import type { GraphQLSchema, OperationTypeNode, DocumentNode } from 'graphql'
import type { IncomingHttpHeaders, Server } from 'http'
import type { AddressInfo } from 'net'
import type { IncomingHttpHeaders } from 'http'
import type { App as ElectronApp } from 'electron'
import { VersionsDataSource } from './sources/VersionsDataSource'
import type { SocketIONamespace, SocketIOServer } from '@packages/socket'
import { globalPubSub } from '.'
import { ProjectLifecycleManager } from './data/ProjectLifecycleManager'
import type { CypressError } from '@packages/errors'
import { ErrorDataSource } from './sources/ErrorDataSource'
import { GraphQLDataSource } from './sources/GraphQLDataSource'
import { RemoteRequestDataSource } from './sources/RemoteRequestDataSource'
import { resetIssuedWarnings } from '@packages/config'
const IS_DEV_ENV = process.env.CYPRESS_INTERNAL_ENV !== 'production'
@@ -99,20 +96,16 @@ export class DataContext {
this.lifecycleManager = new ProjectLifecycleManager(this)
}
get config () {
return this._config
}
get git () {
return this.coreData.currentProjectGitInfo
}
get schema () {
return this._config.schema
}
get schemaCloud () {
return this._config.schemaCloud
}
get isRunMode () {
return this._config.mode === 'run'
return this.config.mode === 'run'
}
get isOpenMode () {
@@ -129,26 +122,6 @@ export class DataContext {
return new RemoteRequestDataSource()
}
get electronApp () {
return this._config.electronApp
}
get electronApi () {
return this._config.electronApi
}
get localSettingsApi () {
return this._config.localSettingsApi
}
get cohortsApi () {
return this._config.cohortsApi
}
get isGlobalMode () {
return this.appData.isGlobalMode
}
get modeOptions () {
return this._modeOptions
}
@@ -157,26 +130,6 @@ export class DataContext {
return this._coreData
}
get user () {
return this.coreData.user
}
get browserList () {
return this.coreData.app.browsers
}
get nodePath () {
return this.coreData.app.nodePath
}
get baseError () {
return this.coreData.diagnostics.error
}
get warnings () {
return this.coreData.diagnostics.warnings
}
@cached
get file () {
return new FileDataSource(this)
@@ -201,19 +154,11 @@ export class DataContext {
return new DataActions(this)
}
get appData () {
return this.coreData.app
}
@cached
get wizard () {
return new WizardDataSource(this)
}
get wizardData () {
return this.coreData.wizard
}
get currentProject () {
return this.coreData.currentProject
}
@@ -237,7 +182,7 @@ export class DataContext {
get cloud () {
return new CloudDataSource({
fetch: (...args) => this.util.fetch(...args),
getUser: () => this.user,
getUser: () => this.coreData.user,
logout: () => this.actions.auth.logout().catch(this.logTraceError),
invalidateClientUrqlCache: () => this.graphql.invalidateClientUrqlCache(this),
headers: {
@@ -276,41 +221,6 @@ export class DataContext {
return new MigrationDataSource(this)
}
get projectsList () {
return this.coreData.app.projects
}
// Servers
setAppServerPort (port: number | undefined) {
this.update((d) => {
d.servers.appServerPort = port ?? null
})
}
setAppSocketServer (socketServer: SocketIOServer | undefined) {
this.update((d) => {
d.servers.appSocketServer?.disconnectSockets(true)
d.servers.appSocketNamespace?.disconnectSockets(true)
d.servers.appSocketServer = socketServer
d.servers.appSocketNamespace = socketServer?.of('/data-context')
})
}
setGqlServer (srv: Server) {
this.update((d) => {
d.servers.gqlServer = srv
d.servers.gqlServerPort = (srv.address() as AddressInfo).port
})
}
setGqlSocketServer (socketServer: SocketIONamespace | undefined) {
this.update((d) => {
d.servers.gqlSocketServer?.disconnectSockets(true)
d.servers.gqlSocketServer = socketServer
})
}
/**
* This will be replaced with Immer, for immutable state updates.
*/
@@ -318,14 +228,6 @@ export class DataContext {
updater(this._coreData)
}
get appServerPort () {
return this.coreData.servers.appServerPort
}
get gqlServerPort () {
return this.coreData.servers.gqlServerPort
}
// Utilities
@cached
@@ -340,13 +242,13 @@ export class DataContext {
get _apis () {
return {
appApi: this._config.appApi,
authApi: this._config.authApi,
browserApi: this._config.browserApi,
projectApi: this._config.projectApi,
electronApi: this._config.electronApi,
localSettingsApi: this._config.localSettingsApi,
cohortsApi: this._config.cohortsApi,
appApi: this.config.appApi,
authApi: this.config.authApi,
browserApi: this.config.browserApi,
projectApi: this.config.projectApi,
electronApi: this.config.electronApi,
localSettingsApi: this.config.localSettingsApi,
cohortsApi: this.config.cohortsApi,
}
}
@@ -427,14 +329,8 @@ export class DataContext {
}
async destroy () {
let destroyGqlServer = () => Promise.resolve()
if (this.coreData.servers.gqlServer?.destroy) {
destroyGqlServer = util.promisify(this.coreData.servers.gqlServer.destroy)
}
return Promise.all([
destroyGqlServer(),
this.actions.servers.destroyGqlServer(),
this._reset(),
])
}
@@ -455,8 +351,8 @@ export class DataContext {
}
_reset () {
this.setAppSocketServer(undefined)
this.setGqlSocketServer(undefined)
this.actions.servers.setAppSocketServer(undefined)
this.actions.servers.setGqlSocketServer(undefined)
resetIssuedWarnings()
@@ -470,10 +366,10 @@ export class DataContext {
async initializeMode () {
assert(!this.coreData.hasInitializedMode)
this.coreData.hasInitializedMode = this._config.mode
if (this._config.mode === 'run') {
this.coreData.hasInitializedMode = this.config.mode
if (this.config.mode === 'run') {
await this.lifecycleManager.initializeRunMode(this.coreData.currentTestingType)
} else if (this._config.mode === 'open') {
} else if (this.config.mode === 'open') {
await this.initializeOpenMode()
await this.lifecycleManager.initializeOpenMode(this.coreData.currentTestingType)
} else {
@@ -1,3 +1,4 @@
import type { BrowserStatus } from '@packages/types'
import type { DataContext } from '..'
export interface AppApiShape {
@@ -21,4 +22,23 @@ export class AppActions {
async ensureAppDataDirExists () {
await this.ctx._apis.appApi.appData.ensure()
}
setBrowserStatus (browserStatus: BrowserStatus) {
this.ctx.update((d) => {
d.app.browserStatus = browserStatus
// when we close the browser null out the user agent
if (browserStatus === 'closed') {
d.app.browserUserAgent = null
}
})
this.ctx.emitter.browserStatusChange()
}
setBrowserUserAgent (userAgent?: string) {
this.ctx.update((d) => {
d.app.browserUserAgent = userAgent || null
})
}
}
@@ -168,6 +168,7 @@ export class DataEmitterActions extends DataEmitterEvents {
*/
toApp () {
this.ctx.coreData.servers.appSocketNamespace?.emit('graphql-refetch')
this.ctx.coreData.servers.cdpSocketNamespace?.emit('graphql-refetch')
}
/**
@@ -183,13 +184,25 @@ export class DataEmitterActions extends DataEmitterEvents {
* source, and respond with the data before the initial hit was able to resolve
*/
notifyClientRefetch (target: 'app' | 'launchpad', operation: string, field: string, variables: any) {
const server = target === 'app' ? this.ctx.coreData.servers.appSocketNamespace : this.ctx.coreData.servers.gqlSocketServer
if (target === 'app') {
this.ctx.coreData.servers.appSocketNamespace?.emit('graphql-refetch', {
field,
operation,
variables,
})
server?.emit('graphql-refetch', {
field,
operation,
variables,
})
this.ctx.coreData.servers.cdpSocketNamespace?.emit('graphql-refetch', {
field,
operation,
variables,
})
} else {
this.ctx.coreData.servers.gqlSocketServer?.emit('graphql-refetch', {
field,
operation,
variables,
})
}
}
/**
@@ -46,7 +46,7 @@ export class ElectronActions {
this.electron.browserWindow?.show()
if (this.isMac) {
this.ctx.electronApp?.dock.show().catch((e) => {
this.ctx.config.electronApp?.dock.show().catch((e) => {
this.ctx.logTraceError(e)
})
} else {
@@ -64,11 +64,11 @@ export class ElectronActions {
}
openExternal (url: string) {
this.ctx.electronApi.openExternal(url)
this.ctx.config.electronApi.openExternal(url)
}
showItemInFolder (url: string) {
this.ctx.electronApi.showItemInFolder(url)
this.ctx.config.electronApi.showItemInFolder(url)
}
showOpenDialog () {
@@ -78,7 +78,7 @@ export class ElectronActions {
properties: ['openDirectory'],
}
return this.ctx.electronApi.showOpenDialog(props)
return this.ctx.config.electronApi.showOpenDialog(props)
.then((obj) => {
// return the first path since there can only ever
// be a single directory selection
@@ -108,13 +108,13 @@ export class ElectronActions {
}
// attach to window so it displays as a modal rather than a standalone window
return this.ctx.electronApi.showSaveDialog(this.electron.browserWindow, props).then((obj) => {
return this.ctx.config.electronApi.showSaveDialog(this.electron.browserWindow, props).then((obj) => {
return obj.filePath || null
})
}
showSystemNotification (title: string, body: string, onClick: () => void) {
const notification = this.ctx.electronApi.createNotification(title, body)
const notification = this.ctx.config.electronApi.createNotification(title, body)
notifications.add(notification)
@@ -176,7 +176,7 @@ export class MigrationActions {
throw Error('cannot do migration without currentProject!')
}
if (this.ctx.isGlobalMode) {
if (this.ctx.coreData.app.isGlobalMode) {
const version = await this.locallyInstalledCypressVersion(this.ctx.currentProject)
if (!version) {
@@ -109,6 +109,7 @@ export class ProjectActions {
d.forceReconfigureProject = null
d.scaffoldedFiles = null
d.app.browserStatus = 'closed'
d.app.browserUserAgent = null
})
// Also clear any data associated with the linked cloud project
@@ -120,12 +121,10 @@ export class ProjectActions {
await this.api.closeActiveProject()
}
private get projects () {
return this.ctx.projectsList
}
private set projects (projects: ProjectShape[]) {
this.ctx.coreData.app.projects = projects
this.ctx.update((d) => {
d.app.projects = projects
})
}
openDirectoryInIDE (projectPath: string) {
@@ -169,11 +168,7 @@ export class ProjectActions {
async loadProjects () {
const projectRoots = await this.api.getProjectRootsFromCache()
this.ctx.update((d) => {
d.app.projects = [...projectRoots]
})
return this.projects
return this.projects = [...projectRoots]
}
async initializeActiveProject (options: OpenProjectLaunchOptions = {}) {
@@ -454,7 +449,7 @@ export class ProjectActions {
return
}
const baseUrlWarning = this.ctx.warnings.find((e) => e.cypressError.type === 'CANNOT_CONNECT_BASE_URL_WARNING')
const baseUrlWarning = this.ctx.coreData.diagnostics.warnings.find((e) => e.cypressError.type === 'CANNOT_CONNECT_BASE_URL_WARNING')
if (baseUrlWarning) {
this.ctx.actions.error.clearWarning(baseUrlWarning.id)
@@ -0,0 +1,54 @@
import util from 'util'
import type { AddressInfo } from 'net'
import type { Server } from 'http'
import type { SocketIONamespace, SocketIOServer } from '@packages/socket'
import type { DataContext } from '..'
import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket'
export class ServersActions {
constructor (private ctx: DataContext) {}
setAppServerPort (port: number | undefined) {
this.ctx.update((d) => {
d.servers.appServerPort = port ?? null
})
}
setAppSocketServer ({ socketIo, cdpIo }: { socketIo?: SocketIOServer, cdpIo?: CDPSocketServer } = { socketIo: undefined, cdpIo: undefined }) {
this.ctx.update((d) => {
d.servers.appSocketServer?.disconnectSockets(true)
d.servers.appSocketNamespace?.disconnectSockets(true)
d.servers.cdpSocketServer?.disconnectSockets(true)
d.servers.cdpSocketNamespace?.disconnectSockets(true)
d.servers.appSocketServer = socketIo
d.servers.appSocketNamespace = socketIo?.of('/data-context')
d.servers.cdpSocketServer = cdpIo
d.servers.cdpSocketNamespace = cdpIo?.of('/data-context')
})
}
setGqlServer (srv: Server) {
this.ctx.update((d) => {
d.servers.gqlServer = srv
d.servers.gqlServerPort = (srv.address() as AddressInfo).port
})
}
setGqlSocketServer (socketServer: SocketIONamespace | undefined) {
this.ctx.update((d) => {
d.servers.gqlSocketServer?.disconnectSockets(true)
d.servers.gqlSocketServer = socketServer
})
}
async destroyGqlServer () {
const destroy = this.ctx.coreData.servers.gqlServer?.destroy
if (!destroy) {
return
}
return util.promisify(destroy)()
}
}
@@ -20,10 +20,6 @@ export class WizardActions {
return this.ctx.currentProject
}
private get data () {
return this.ctx.wizardData
}
setFramework (framework: Cypress.ResolvedComponentFrameworkDefinition | null): void {
const next = this.ctx.coreData.wizard.frameworks.find((x) => x.type === framework?.type)
@@ -17,5 +17,6 @@ export * from './LocalSettingsActions'
export * from './MigrationActions'
export * from './NotificationActions'
export * from './ProjectActions'
export * from './ServersActions'
export * from './VersionsActions'
export * from './WizardActions'
@@ -362,7 +362,7 @@ export class ProjectConfigManager {
}
this._eventsIpc = new ProjectConfigIpc(
this.options.ctx.nodePath,
this.options.ctx.coreData.app.nodePath,
this.options.projectRoot,
this.configFilePath,
this.options.configFile,
@@ -8,6 +8,7 @@ import type { Server } from 'http'
import type { ErrorWrapperSource } from '@packages/errors'
import type { EventCollectorSource, GitDataSource, LegacyCypressConfigJson } from '../sources'
import { machineId as getMachineId } from 'node-machine-id'
import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket'
export type Maybe<T> = T | null | undefined
@@ -23,6 +24,18 @@ export interface ProjectShape {
savedState?: () => Promise<Maybe<SavedStateShape>>
}
export interface ServersDataShape {
appServer?: Maybe<Server>
appServerPort?: Maybe<number>
appSocketServer?: Maybe<SocketIOServer>
appSocketNamespace?: Maybe<SocketIONamespace>
cdpSocketServer?: CDPSocketServer | undefined
cdpSocketNamespace?: CDPSocketServer | undefined
gqlServer?: Maybe<Server>
gqlServerPort?: Maybe<number>
gqlSocketServer?: Maybe<SocketIONamespace>
}
export interface DevStateShape {
refreshState: null | string
}
@@ -63,6 +76,7 @@ export interface AppDataShape {
projects: ProjectShape[]
nodePath: Maybe<string>
browserStatus: BrowserStatus
browserUserAgent: string | null
relaunchBrowser: boolean
}
@@ -79,6 +93,7 @@ export interface WizardDataShape {
export interface MigrationDataShape {
// TODO: have the model of migration here
step: MigrationStep
videoEmbedHtml: string | null
legacyConfigForMigration?: LegacyCypressConfigJson | null
filteredSteps: MigrationStep[]
flags: {
@@ -132,15 +147,7 @@ export interface CoreDataShape {
machineId: Promise<string | null>
machineBrowsers: Promise<FoundBrowser[]> | null
allBrowsers: Promise<FoundBrowser[]> | null
servers: {
appServer?: Maybe<Server>
appServerPort?: Maybe<number>
appSocketServer?: Maybe<SocketIOServer>
appSocketNamespace?: Maybe<SocketIONamespace>
gqlServer?: Maybe<Server>
gqlServerPort?: Maybe<number>
gqlSocketServer?: Maybe<SocketIONamespace>
}
servers: ServersDataShape
hasInitializedMode: 'run' | 'open' | null
cloudGraphQLError: ErrorWrapperSource | null
dev: DevStateShape
@@ -188,6 +195,7 @@ export function makeCoreData (modeOptions: Partial<AllModeOptions> = {}): CoreDa
projects: [],
nodePath: modeOptions.userNodePath,
browserStatus: 'closed',
browserUserAgent: null,
relaunchBrowser: false,
},
localSettings: {
@@ -214,6 +222,7 @@ export function makeCoreData (modeOptions: Partial<AllModeOptions> = {}): CoreDa
},
migration: {
step: 'renameAuto',
videoEmbedHtml: null,
legacyConfigForMigration: null,
filteredSteps: [...MIGRATION_STEPS],
flags: {
@@ -1,9 +1,9 @@
import type { FoundBrowser, BrowserStatus } from '@packages/types'
import os from 'os'
import execa from 'execa'
import type { DataContext } from '..'
import _ from 'lodash'
import os from 'os'
import type { FoundBrowser } from '@packages/types'
import type { DataContext } from '..'
let isPowerShellAvailable: undefined | boolean
let powerShellPromise: Promise<void> | undefined
@@ -131,12 +131,4 @@ export class BrowserDataSource {
isVersionSupported (obj: FoundBrowser) {
return Boolean(!obj.unsupportedVersion)
}
setBrowserStatus (browserStatus: BrowserStatus) {
this.ctx.update((d) => {
d.app.browserStatus = browserStatus
})
this.ctx.emitter.browserStatusChange()
}
}
@@ -170,7 +170,7 @@ export class CloudDataSource {
delegateCloudField <F extends CloudQueryField> (params: CloudExecuteDelegateFieldParams<F>) {
return delegateToSchema({
operation: 'query',
schema: params.ctx.schemaCloud,
schema: params.ctx.config.schemaCloud,
fieldName: params.field,
fieldNodes: params.info.fieldNodes,
info: params.info,
@@ -74,7 +74,7 @@ export class GraphQLDataSource {
},
InlineFragment: (node) => {
// Remove any non-cloud types from the node
if (node.typeCondition && !ctx.schemaCloud.getType(node.typeCondition.name.value)) {
if (node.typeCondition && !ctx.config.schemaCloud.getType(node.typeCondition.name.value)) {
return null
}
@@ -85,7 +85,7 @@ export class GraphQLDataSource {
// Execute the node field against the cloud schema
return execute({
schema: ctx.schemaCloud,
schema: ctx.config.schemaCloud,
contextValue: ctx,
variableValues: info.variableValues,
document: {
@@ -56,15 +56,18 @@ export class HtmlDataSource {
'reporterUrl',
'namespace',
'socketIoRoute',
'protocolEnabled',
'hideCommandLog',
'hideRunnerUi',
]
return _.pick(cfg, keys)
}
async makeServeConfig () {
const propertiesFromLegacyConfig = this.getPropertiesFromServerConfig(this.ctx._apis.projectApi.getConfig())
const propertiesFromServerConfig = this.getPropertiesFromServerConfig(this.ctx._apis.projectApi.getConfig())
let cfg = { ...propertiesFromLegacyConfig }
let cfg = { ...propertiesFromServerConfig }
try {
cfg = {
@@ -91,7 +94,8 @@ export class HtmlDataSource {
projectName: this.ctx.lifecycleManager.projectTitle,
namespace: cfg.namespace || '__cypress-string',
base64Config: Buffer.from(JSON.stringify(cfg)).toString('base64'),
hideCommandLog: cfg.env?.NO_COMMAND_LOG === 1,
hideCommandLog: cfg.hideCommandLog,
hideRunnerUi: cfg.hideRunnerUi,
}
}
@@ -96,6 +96,37 @@ export class MigrationDataSource {
return this.ctx.lifecycleManager.metaState.needsCypressJsonMigration && Boolean(legacyConfigFileExists)
}
async getVideoEmbedHtml () {
if (this.ctx.coreData.migration.videoEmbedHtml) {
return this.ctx.coreData.migration.videoEmbedHtml
}
const versionData = await this.ctx.versions.versionData()
const embedOnLink = `https://on.cypress.io/v13-video-embed/${versionData.current.version}`
debug(`Getting videoEmbedHtml at link: ${embedOnLink}`)
// Time out request if it takes longer than 3 seconds
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 3000)
try {
const response = await this.ctx.util.fetch(embedOnLink, { method: 'GET', signal: controller.signal })
const { videoHtml } = await response.json()
this.ctx.update((d) => {
d.migration.videoEmbedHtml = videoHtml
})
return videoHtml
} catch {
// fail silently, no user-facing error is needed
return null
} finally {
clearTimeout(timeoutId)
}
}
async getComponentTestingMigrationStatus () {
debug('getComponentTestingMigrationStatus: start')
if (!this.legacyConfig || !this.ctx.currentProject) {
@@ -306,7 +306,7 @@ export class RemoteRequestDataSource {
const fieldNodes = this.#getDataFieldNodes(info)
const referencedVariableValues = this.#getReferencedVariables(fieldNodes, info.operation.variableDefinitions ?? [])
const queryFieldDef = ctx.schemaCloud.getQueryType()?.getFields()[fieldConfig.remoteQueryField]
const queryFieldDef = ctx.config.schemaCloud.getQueryType()?.getFields()[fieldConfig.remoteQueryField]
assert(queryFieldDef, `Unknown remote query field ${fieldConfig.remoteQueryField}`)
@@ -131,7 +131,7 @@ export class VersionsDataSource {
debug('#getLatestVersion')
const preferences = await this.ctx.localSettingsApi.getPreferences()
const preferences = await this.ctx.config.localSettingsApi.getPreferences()
const notificationPreferences: ('started' | 'failing' | 'passed' | 'failed' | 'cancelled' | 'errored')[] = [
...preferences.notifyWhenRunCompletes ?? [],
]
@@ -153,7 +153,7 @@ export class VersionsDataSource {
'x-arch': os.arch(),
'x-notifications': notificationPreferences.join(','),
'x-initial-launch': String(this._initialLaunch),
'x-logged-in': String(!!this.ctx.user),
'x-logged-in': String(!!this.ctx.coreData.user),
}
if (this._currentTestingType) {
@@ -241,7 +241,7 @@ const resolvedOptions: Array<ResolvedConfigOption> = [
canUpdateDuringTestTime: false,
}, {
name: 'video',
defaultValue: true,
defaultValue: false,
canUpdateDuringTestTime: false,
}, {
name: 'videoCompression',
@@ -23,7 +23,7 @@ describe('CohortsActions', () => {
const cohort = await actions.getCohort(name)
expect(cohort).to.be.undefined
expect(ctx.cohortsApi.getCohort).to.have.been.calledWith(name)
expect(ctx.config.cohortsApi.getCohort).to.have.been.calledWith(name)
})
it('should return cohort if in cache', async () => {
@@ -37,7 +37,7 @@ describe('CohortsActions', () => {
const cohortReturned = await actions.getCohort(cohort.name)
expect(cohortReturned).to.eq(cohort)
expect(ctx.cohortsApi.getCohort).to.have.been.calledWith(cohort.name)
expect(ctx.config.cohortsApi.getCohort).to.have.been.calledWith(cohort.name)
})
})
@@ -50,7 +50,7 @@ describe('CohortsActions', () => {
const pickedCohort = await actions.determineCohort(cohortConfig.name, cohortConfig.cohorts)
expect(ctx.cohortsApi.insertCohort).to.have.been.calledOnceWith({ name: cohortConfig.name, cohort: match.string })
expect(ctx.config.cohortsApi.insertCohort).to.have.been.calledOnceWith({ name: cohortConfig.name, cohort: match.string })
expect(cohortConfig.cohorts.includes(pickedCohort.cohort)).to.be.true
})
})
@@ -315,7 +315,7 @@ describe('CloudDataSource', () => {
const result = await execute({
rootValue: {},
document: CLOUD_PROJECT_QUERY,
schema: ctx.schema,
schema: ctx.config.schema,
contextValue: ctx,
})
@@ -333,7 +333,7 @@ describe('CloudDataSource', () => {
const result2 = await execute({
rootValue: {},
document: CLOUD_PROJECT_QUERY,
schema: ctx.schema,
schema: ctx.config.schema,
contextValue: ctx,
})
@@ -25,7 +25,7 @@ describe('GraphQLDataSource', () => {
ctx.project.projectId = async () => 'abc123'
pushFragmentIterator = await Promise.resolve(subscribe({
schema: ctx.schema,
schema: ctx.config.schema,
contextValue: ctx,
document: parse(`subscription {
pushFragment {
@@ -47,7 +47,7 @@ describe('GraphQLDataSource', () => {
function executeQuery (query: string) {
return Promise.resolve(execute({
document: parse(query),
schema: ctx.schema,
schema: ctx.config.schema,
contextValue: ctx,
}))
}
@@ -277,7 +277,7 @@ describe('VersionsDataSource', () => {
})
it('generates x-notifications header', async () => {
(ctx.localSettingsApi.getPreferences as sinon.SinonStub).callsFake(() => {
(ctx.config.localSettingsApi.getPreferences as sinon.SinonStub).callsFake(() => {
return {
notifyWhenRunCompletes: ['errored'],
notifyWhenRunStarts: true,
-1
View File
@@ -18,7 +18,6 @@ export default defineConfig({
reporterOptions: {
configFile: '../../mocha-reporter-config.json',
},
videoCompression: false, // turn off video compression for CI
e2e: {
experimentalOriginDependencies: true,
experimentalModifyObstructiveThirdPartyCode: true,

Some files were not shown because too many files have changed in this diff Show More