mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-21 14:41:00 -06:00
Merge branch 'develop' into feature-multidomain
This commit is contained in:
56
.github/CODEOWNERS
vendored
56
.github/CODEOWNERS
vendored
@@ -1,30 +1,30 @@
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
|
||||
# Test Runner team are owners of code within root packages concerning e2e, cli, and other utils
|
||||
/.github/ @cypress-io/test-runner
|
||||
/browser-versions.json @cypress-io/test-runner
|
||||
/cli/ @cypress-io/test-runner
|
||||
/packages/coffee/ @cypress-io/test-runner
|
||||
/packages/datetime-utils/ @cypress-io/test-runner
|
||||
/packages/desktop-gui/ @cypress-io/test-runner
|
||||
/packages/driver/ @cypress-io/test-runner
|
||||
/packages/electron/ @cypress-io/test-runner
|
||||
/packages/example/ @cypress-io/test-runner
|
||||
/packages/extension/ @cypress-io/test-runner
|
||||
/packages/https-proxy/ @cypress-io/test-runner
|
||||
/packages/launcher/ @cypress-io/test-runner
|
||||
/packages/net-stubbing/ @cypress-io/test-runner
|
||||
/packages/network/ @cypress-io/test-runner
|
||||
/packages/proxy/ @cypress-io/test-runner
|
||||
/packages/reporter/ @cypress-io/test-runner
|
||||
/packages/rewriter/ @cypress-io/test-runner
|
||||
/packages/root/ @cypress-io/test-runner
|
||||
/packages/runner/ @cypress-io/test-runner
|
||||
/packages/runner-shared/ @cypress-io/test-runner
|
||||
/packages/server/ @cypress-io/test-runner
|
||||
/packages/socket/ @cypress-io/test-runner
|
||||
/packages/static/ @cypress-io/test-runner
|
||||
/packages/ts/ @cypress-io/test-runner
|
||||
/packages/ui-components/ @cypress-io/test-runner
|
||||
/packages/web-config/ @cypress-io/test-runner
|
||||
/scripts/ @cypress-io/test-runner
|
||||
# End-to-end team are owners of code within root packages concerning e2e, cli, and other utils
|
||||
/.github/ @cypress-io/end-to-end
|
||||
/browser-versions.json @cypress-io/end-to-end
|
||||
/cli/ @cypress-io/end-to-end
|
||||
/packages/coffee/ @cypress-io/end-to-end
|
||||
/packages/datetime-utils/ @cypress-io/end-to-end
|
||||
/packages/desktop-gui/ @cypress-io/end-to-end
|
||||
/packages/driver/ @cypress-io/end-to-end
|
||||
/packages/electron/ @cypress-io/end-to-end
|
||||
/packages/example/ @cypress-io/end-to-end
|
||||
/packages/extension/ @cypress-io/end-to-end
|
||||
/packages/https-proxy/ @cypress-io/end-to-end
|
||||
/packages/launcher/ @cypress-io/end-to-end
|
||||
/packages/net-stubbing/ @cypress-io/end-to-end
|
||||
/packages/network/ @cypress-io/end-to-end
|
||||
/packages/proxy/ @cypress-io/end-to-end
|
||||
/packages/reporter/ @cypress-io/end-to-end
|
||||
/packages/rewriter/ @cypress-io/end-to-end
|
||||
/packages/root/ @cypress-io/end-to-end
|
||||
/packages/runner/ @cypress-io/end-to-end
|
||||
/packages/runner-shared/ @cypress-io/end-to-end
|
||||
/packages/server/ @cypress-io/end-to-end
|
||||
/packages/socket/ @cypress-io/end-to-end
|
||||
/packages/static/ @cypress-io/end-to-end
|
||||
/packages/ts/ @cypress-io/end-to-end
|
||||
/packages/ui-components/ @cypress-io/end-to-end
|
||||
/packages/web-config/ @cypress-io/end-to-end
|
||||
/scripts/ @cypress-io/end-to-end
|
||||
|
||||
@@ -281,7 +281,7 @@ Here is a list of the core packages in this repository with a short description,
|
||||
| [extension](./packages/extension) | `@packages/extension` | The Cypress Chrome browser extension |
|
||||
| [https-proxy](./packages/https-proxy) | `@packages/https-proxy` | This does https proxy for handling http certs and traffic. |
|
||||
| [net-stubbing](./packages/net-stubbing) | `@packages/net-stubbing` | Contains server side code for Cypress' network stubbing features. |
|
||||
| [network](./packages/networ ) | `@packages/network` | Various utilities related to networking. |
|
||||
| [network](./packages/network) | `@packages/network` | Various utilities related to networking. |
|
||||
| [proxy](./packages/proxy) | `@packages/proxy` | Code for Cypress' network proxy layer. |
|
||||
| [launcher](./packages/launcher) | `@packages/launcher` | Finds and launches browsers installed on your system. |
|
||||
| [reporter](./packages/reporter) | `@packages/reporter` | The reporter shows the running results of the tests (The Command Log UI). |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"chrome:beta": "96.0.4664.45",
|
||||
"chrome:stable": "96.0.4664.45"
|
||||
"chrome:beta": "97.0.4692.56",
|
||||
"chrome:stable": "96.0.4664.110"
|
||||
}
|
||||
|
||||
50
circle.yml
50
circle.yml
@@ -29,6 +29,7 @@ mainBuildFilters: &mainBuildFilters
|
||||
only:
|
||||
- develop
|
||||
- 10.0-release
|
||||
- test-binary-downstream-windows
|
||||
|
||||
# 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
|
||||
@@ -37,7 +38,6 @@ macWorkflowFilters: &mac-workflow-filters
|
||||
when:
|
||||
or:
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
- equal: [ tgriesser/fix/patch-resolutions, << pipeline.git.branch >> ]
|
||||
- equal: [ renovate/cypress-request-2.x, << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: "-release$"
|
||||
@@ -48,7 +48,7 @@ windowsWorkflowFilters: &windows-workflow-filters
|
||||
or:
|
||||
- equal: [ master, << pipeline.git.branch >> ]
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
- equal: [ tgriesser/fix/patch-resolutions, << pipeline.git.branch >> ]
|
||||
- equal: [ test-binary-downstream-windows, << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: "-release$"
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -1588,7 +1588,7 @@ jobs:
|
||||
- run:
|
||||
name: Check current branch to persist artifacts
|
||||
command: |
|
||||
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "tgriesser/fix/patch-resolutions" ]]; then
|
||||
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "test-binary-downstream-windows" ]]; then
|
||||
echo "Not uploading artifacts or posting install comment for this branch."
|
||||
circleci-agent step halt
|
||||
fi
|
||||
@@ -1664,7 +1664,12 @@ jobs:
|
||||
|
||||
test-binary-and-npm-against-other-projects:
|
||||
<<: *defaults
|
||||
resource_class: medium
|
||||
parameters:
|
||||
<<: *defaultsParameters
|
||||
resource_class:
|
||||
type: string
|
||||
default: medium
|
||||
resource_class: << parameters.resource_class >>
|
||||
steps:
|
||||
# needs uploaded NPM and test binary
|
||||
- restore_cached_workspace
|
||||
@@ -1673,23 +1678,14 @@ jobs:
|
||||
- run: ls -la binary-url.json npm-package-url.json
|
||||
- run: cat binary-url.json
|
||||
- run: cat npm-package-url.json
|
||||
- run: mkdir /tmp/testing
|
||||
- run:
|
||||
name: create dummy package
|
||||
working_directory: /tmp/testing
|
||||
command: npm init -y
|
||||
- run:
|
||||
# install NPM from unique urls
|
||||
name: Install Cypress
|
||||
name: Install Cypress Binary in Dummy Package
|
||||
command: |
|
||||
node scripts/test-unique-npm-and-binary.js \
|
||||
--npm npm-package-url.json \
|
||||
--binary binary-url.json \
|
||||
--cwd /tmp/testing
|
||||
- run:
|
||||
name: Verify Cypress binary
|
||||
working_directory: /tmp/testing
|
||||
command: $(yarn bin)/cypress verify
|
||||
- run:
|
||||
name: Running other test projects with new NPM package and binary
|
||||
command: |
|
||||
@@ -1720,19 +1716,19 @@ jobs:
|
||||
- run:
|
||||
name: Cypress version
|
||||
working_directory: test-binary
|
||||
command: $(yarn bin)/cypress version
|
||||
command: $(yarn bin cypress) version
|
||||
- run:
|
||||
name: Verify Cypress binary
|
||||
working_directory: test-binary
|
||||
command: $(yarn bin)/cypress verify
|
||||
command: $(yarn bin cypress) verify
|
||||
- run:
|
||||
name: Cypress help
|
||||
working_directory: test-binary
|
||||
command: $(yarn bin)/cypress help
|
||||
command: $(yarn bin cypress) help
|
||||
- run:
|
||||
name: Cypress info
|
||||
working_directory: test-binary
|
||||
command: $(yarn bin)/cypress info
|
||||
command: $(yarn bin cypress) info
|
||||
- store-npm-logs
|
||||
|
||||
test-npm-module-on-minimum-node-version:
|
||||
@@ -1891,7 +1887,7 @@ jobs:
|
||||
CYPRESS_PROJECT_ID=$TEST_TINY_PROJECT_ID \
|
||||
CYPRESS_RECORD_KEY=$TEST_TINY_RECORD_KEY \
|
||||
CYPRESS_INTERNAL_ENV=staging \
|
||||
$(yarn bin)/cypress run --record
|
||||
$(yarn bin cypress) run --record
|
||||
- store-npm-logs
|
||||
|
||||
test-binary-against-recipes-firefox:
|
||||
@@ -2026,11 +2022,11 @@ jobs:
|
||||
- run:
|
||||
name: Cypress help
|
||||
working_directory: test-binary
|
||||
command: $(yarn bin)/cypress help
|
||||
command: $(yarn bin cypress) help
|
||||
- run:
|
||||
name: Cypress info
|
||||
working_directory: test-binary
|
||||
command: $(yarn bin)/cypress info
|
||||
command: $(yarn bin cypress) info
|
||||
- run:
|
||||
name: Add Cypress demo
|
||||
working_directory: test-binary
|
||||
@@ -2038,11 +2034,11 @@ jobs:
|
||||
- run:
|
||||
name: Verify Cypress binary
|
||||
working_directory: test-binary
|
||||
command: DEBUG=cypress:cli $(yarn bin)/cypress verify
|
||||
command: DEBUG=cypress:cli $(yarn bin cypress) verify
|
||||
- run:
|
||||
name: Run Cypress binary
|
||||
working_directory: test-binary
|
||||
command: DEBUG=cypress:cli $(yarn bin)/cypress run
|
||||
command: DEBUG=cypress:cli $(yarn bin cypress) run
|
||||
- store-npm-logs
|
||||
|
||||
linux-workflow: &linux-workflow
|
||||
@@ -2432,6 +2428,14 @@ windows-workflow: &windows-workflow
|
||||
requires:
|
||||
- windows-build
|
||||
|
||||
- test-binary-and-npm-against-other-projects:
|
||||
context: test-runner:trigger-test-jobs
|
||||
name: windows-test-binary-and-npm-against-other-projects
|
||||
executor: windows
|
||||
resource_class: windows.medium
|
||||
requires:
|
||||
- windows-create-build-artifacts
|
||||
|
||||
workflows:
|
||||
linux:
|
||||
<<: *linux-workflow
|
||||
|
||||
@@ -15,7 +15,7 @@ const logger = require('../logger')
|
||||
const xvfb = require('../exec/xvfb')
|
||||
const state = require('./state')
|
||||
|
||||
const VERIFY_TEST_RUNNER_TIMEOUT_MS = 30000
|
||||
const VERIFY_TEST_RUNNER_TIMEOUT_MS = process.env.CYPRESS_VERIFY_TIMEOUT || 30000
|
||||
|
||||
const checkExecutable = (binaryDir) => {
|
||||
const executable = state.getPathToExecutable(binaryDir)
|
||||
|
||||
@@ -72,6 +72,14 @@ context('lib/tasks/verify', () => {
|
||||
expect(verify.VERIFY_TEST_RUNNER_TIMEOUT_MS).to.be.gt(10000)
|
||||
})
|
||||
|
||||
it('accepts custom verify task timeout', () => {
|
||||
process.env.CYPRESS_VERIFY_TIMEOUT = '500000'
|
||||
delete require.cache[require.resolve(`${lib}/tasks/verify`)]
|
||||
const newVerifyInstance = require(`${lib}/tasks/verify`)
|
||||
|
||||
expect(newVerifyInstance.VERIFY_TEST_RUNNER_TIMEOUT_MS).to.be.gte(500000)
|
||||
})
|
||||
|
||||
it('logs error and exits when no version of Cypress is installed', () => {
|
||||
return verify
|
||||
.start()
|
||||
|
||||
62
cli/types/cypress.d.ts
vendored
62
cli/types/cypress.d.ts
vendored
@@ -7,13 +7,38 @@ declare namespace Cypress {
|
||||
type HttpMethod = string
|
||||
type RequestBody = string | object
|
||||
type ViewportOrientation = 'portrait' | 'landscape'
|
||||
type PrevSubject = 'optional' | 'element' | 'document' | 'window'
|
||||
type PrevSubject = keyof PrevSubjectMap
|
||||
type TestingType = 'e2e' | 'component'
|
||||
type PluginConfig = (on: PluginEvents, config: PluginConfigOptions) => void | ConfigOptions | Promise<ConfigOptions>
|
||||
|
||||
interface PrevSubjectMap<O = unknown> {
|
||||
optional: O
|
||||
element: JQuery
|
||||
document: Document
|
||||
window: Window
|
||||
}
|
||||
|
||||
interface CommandOptions {
|
||||
prevSubject: boolean | PrevSubject | PrevSubject[]
|
||||
}
|
||||
interface CommandFn<T extends keyof ChainableMethods> {
|
||||
(this: Mocha.Context, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
|
||||
}
|
||||
interface CommandFnWithSubject<T extends keyof ChainableMethods, S> {
|
||||
(this: Mocha.Context, prevSubject: S, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
|
||||
}
|
||||
interface CommandOriginalFn<T extends keyof ChainableMethods> extends CallableFunction {
|
||||
(...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]>
|
||||
}
|
||||
interface CommandOriginalFnWithSubject<T extends keyof ChainableMethods, S> extends CallableFunction {
|
||||
(prevSubject: S, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]>
|
||||
}
|
||||
interface CommandFnWithOriginalFn<T extends keyof Chainable> {
|
||||
(this: Mocha.Context, originalFn: CommandOriginalFn<T>, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
|
||||
}
|
||||
interface CommandFnWithOriginalFnAndSubject<T extends keyof Chainable, S> {
|
||||
(this: Mocha.Context, originalFn: CommandOriginalFnWithSubject<T, S>, prevSubject: S, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
|
||||
}
|
||||
interface ObjectLike {
|
||||
[key: string]: any
|
||||
}
|
||||
@@ -294,6 +319,11 @@ declare namespace Cypress {
|
||||
*/
|
||||
LocalStorage: LocalStorage
|
||||
|
||||
/**
|
||||
* Internal class for session management.
|
||||
*/
|
||||
session: Session
|
||||
|
||||
/**
|
||||
* Current testing type, determined by the Test Runner chosen to run.
|
||||
*/
|
||||
@@ -328,7 +358,7 @@ declare namespace Cypress {
|
||||
// 60000
|
||||
```
|
||||
*/
|
||||
config<K extends keyof ConfigOptions>(key: K): ResolvedConfigOptions[K]
|
||||
config<K extends keyof Config>(key: K): Config[K]
|
||||
/**
|
||||
* Sets one configuration value.
|
||||
* @see https://on.cypress.io/config
|
||||
@@ -337,7 +367,7 @@ declare namespace Cypress {
|
||||
Cypress.config('viewportWidth', 800)
|
||||
```
|
||||
*/
|
||||
config<K extends keyof ConfigOptions>(key: K, value: ResolvedConfigOptions[K]): void
|
||||
config<K extends keyof TestConfigOverrides>(key: K, value: TestConfigOverrides[K]): void
|
||||
/**
|
||||
* Sets multiple configuration values at once.
|
||||
* @see https://on.cypress.io/config
|
||||
@@ -420,9 +450,16 @@ declare namespace Cypress {
|
||||
* @see https://on.cypress.io/api/commands
|
||||
*/
|
||||
Commands: {
|
||||
add<T extends keyof Chainable>(name: T, fn: Chainable[T]): void
|
||||
add<T extends keyof Chainable>(name: T, options: CommandOptions, fn: Chainable[T]): void
|
||||
overwrite<T extends keyof Chainable>(name: T, fn: Chainable[T]): void
|
||||
add<T extends keyof Chainable>(name: T, fn: CommandFn<T>): void
|
||||
add<T extends keyof Chainable>(name: T, options: CommandOptions & {prevSubject: false}, fn: CommandFn<T>): void
|
||||
add<T extends keyof Chainable, S extends PrevSubject>(
|
||||
name: T, options: CommandOptions & { prevSubject: true | S | ['optional'] }, fn: CommandFnWithSubject<T, PrevSubjectMap[S]>,
|
||||
): void
|
||||
add<T extends keyof Chainable, S extends PrevSubject>(
|
||||
name: T, options: CommandOptions & { prevSubject: S[] }, fn: CommandFnWithSubject<T, PrevSubjectMap<void>[S]>,
|
||||
): void
|
||||
overwrite<T extends keyof Chainable>(name: T, fn: CommandFnWithOriginalFn<T>): void
|
||||
overwrite<T extends keyof Chainable, S extends PrevSubject>(name: T, fn: CommandFnWithOriginalFnAndSubject<T, PrevSubjectMap[S]>): void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2248,6 +2285,12 @@ declare namespace Cypress {
|
||||
$$<TElement extends Element = HTMLElement>(selector: JQuery.Selector, context?: Element | Document | JQuery): JQuery<TElement>
|
||||
}
|
||||
|
||||
type ChainableMethods<Subject = any> = {
|
||||
[P in keyof Chainable<Subject>]: Chainable<Subject>[P] extends ((...args: any[]) => any)
|
||||
? Chainable<Subject>[P]
|
||||
: never
|
||||
}
|
||||
|
||||
interface SinonSpyAgent<A extends sinon.SinonSpy> {
|
||||
log(shouldOutput?: boolean): Omit<A, 'withArgs'> & Agent<A>
|
||||
|
||||
@@ -2879,7 +2922,7 @@ declare namespace Cypress {
|
||||
xhrUrl: string
|
||||
}
|
||||
|
||||
interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
|
||||
interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'blockHosts' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'numTestsKeptInMemory' | 'pageLoadTimeout' | 'redirectionLimit' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'screenshotOnRunFailure' | 'slowTestThreshold' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
|
||||
browser?: IsBrowserMatcher | IsBrowserMatcher[]
|
||||
keystrokeDelay?: number
|
||||
}
|
||||
@@ -3079,6 +3122,11 @@ declare namespace Cypress {
|
||||
onAnyAbort(route: RouteOptions, proxy: any): void
|
||||
}
|
||||
|
||||
interface Session {
|
||||
// Clear all saved sessions and re-run the current spec file.
|
||||
clearAllSavedSessions: () => Promise<void>
|
||||
}
|
||||
|
||||
type SameSiteStatus = 'no_restriction' | 'strict' | 'lax'
|
||||
|
||||
interface SetCookieOptions extends Loggable, Timeoutable {
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace CypressIsCyTests {
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable {
|
||||
newCommand: (arg: string) => void
|
||||
newCommand: (arg: string) => Chainable<number>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,20 +74,106 @@ namespace CypressCommandsTests {
|
||||
arg
|
||||
return
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: true }, (arg) => {
|
||||
Cypress.Commands.add('newCommand', (arg) => {
|
||||
// $ExpectType string
|
||||
arg
|
||||
})
|
||||
Cypress.Commands.add('newCommand', function(arg) {
|
||||
this // $ExpectType Context
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: true }, (subject, arg) => {
|
||||
subject // $ExpectType unknown
|
||||
arg // $ExpectType string
|
||||
return
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: false }, (arg) => {
|
||||
arg // $ExpectType string
|
||||
return
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: 'optional' }, (subject, arg) => {
|
||||
subject // $ExpectType unknown
|
||||
arg // $ExpectType string
|
||||
return
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: 'optional' }, (subject, arg) => {
|
||||
subject // $ExpectType unknown
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: ['optional'] }, (subject, arg) => {
|
||||
subject // $ExpectType unknown
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: 'document' }, (subject, arg) => {
|
||||
subject // $ExpectType Document
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: 'window' }, (subject, arg) => {
|
||||
subject // $ExpectType Window
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: 'element' }, (subject, arg) => {
|
||||
subject // $ExpectType JQuery<HTMLElement>
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: ['element'] }, (subject, arg) => {
|
||||
subject // $ExpectType JQuery<HTMLElement>
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: ['element', 'document', 'window'] }, (subject, arg) => {
|
||||
if (subject instanceof Window) {
|
||||
subject // $ExpectType Window
|
||||
} else if (subject instanceof Document) {
|
||||
subject // $ExpectType Document
|
||||
} else {
|
||||
subject // $ExpectType JQuery<HTMLElement>
|
||||
}
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', { prevSubject: ['window', 'document', 'optional', 'element'] }, (subject, arg) => {
|
||||
if (subject instanceof Window) {
|
||||
subject // $ExpectType Window
|
||||
} else if (subject instanceof Document) {
|
||||
subject // $ExpectType Document
|
||||
} else if (subject) {
|
||||
subject // $ExpectType JQuery<HTMLElement>
|
||||
} else {
|
||||
subject // $ExpectType void
|
||||
}
|
||||
arg // $ExpectType string
|
||||
})
|
||||
Cypress.Commands.add('newCommand', (arg) => {
|
||||
// $ExpectType string
|
||||
arg
|
||||
return new Promise((resolve) => {})
|
||||
return cy.wrap(new Promise<number>((resolve) => { resolve(5) }))
|
||||
})
|
||||
Cypress.Commands.overwrite('newCommand', (arg) => {
|
||||
// $ExpectType string
|
||||
arg
|
||||
return
|
||||
Cypress.Commands.overwrite('newCommand', (originalFn, arg) => {
|
||||
arg // $ExpectType string
|
||||
originalFn // $ExpectedType Chainable['newCommand']
|
||||
originalFn(arg) // $ExpectType Chainable<number>
|
||||
})
|
||||
Cypress.Commands.overwrite('newCommand', function(originalFn, arg) {
|
||||
this // $ExpectType Context
|
||||
arg // $ExpectType string
|
||||
originalFn // $ExpectedType Chainable['newCommand']
|
||||
originalFn.apply(this, [arg]) // $ExpectType Chainable<number>
|
||||
})
|
||||
Cypress.Commands.overwrite<'type', 'element'>('type', (originalFn, element, text, options?: Partial<Cypress.TypeOptions & {sensitive: boolean}>) => {
|
||||
element // $ExpectType JQuery<HTMLElement>
|
||||
text // $ExpectType string
|
||||
|
||||
if (options && options.sensitive) {
|
||||
// turn off original log
|
||||
options.log = false
|
||||
// create our own log with masked message
|
||||
Cypress.log({
|
||||
$el: element,
|
||||
name: 'type',
|
||||
message: '*'.repeat(text.length),
|
||||
})
|
||||
}
|
||||
|
||||
return originalFn(element, text, options)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,36 +11,46 @@ The `@cypress/`-namespaced NPM packages that live inside the [`/npm`](../npm) di
|
||||
### Prerequisites
|
||||
|
||||
- Ensure you have the following permissions set up:
|
||||
- An AWS account with permission to create AWS access keys for the Cypress CDN.
|
||||
- Permissions for your npm account to publish the `cypress` package.
|
||||
- Permissions to update releases in ZenHub.
|
||||
- An AWS account with permission to create AWS access keys for the Cypress CDN.
|
||||
- Permissions for your npm account to publish the `cypress` package.
|
||||
- Permissions to update releases in ZenHub.
|
||||
|
||||
- Set up the following environment variables:
|
||||
- Cypress AWS access key and secret in `aws_credentials_json`, which looks like this:
|
||||
```text
|
||||
aws_credentials_json={"bucket":"cdn.cypress.io","folder":"desktop","key":"...","secret":"..."}
|
||||
```
|
||||
- Cypress AWS access key and secret in `aws_credentials_json`, which looks like this:
|
||||
|
||||
```text
|
||||
aws_credentials_json='{"bucket":"cdn.cypress.io","folder":"desktop","key":"...","secret":"..."}'
|
||||
```
|
||||
|
||||
- A [GitHub token](https://github.com/settings/tokens) and a [CircleCI token](https://circleci.com/account/api) in `ci_json`:
|
||||
```text
|
||||
ci_json={"githubToken":"...","circleToken":"..."}
|
||||
```
|
||||
- You'll also need to put the GitHub token under its own variable and get a [ZenHub API token](https://app.zenhub.com/dashboard/tokens) for the `release-automations` step.
|
||||
```text
|
||||
GITHUB_TOKEN="..."
|
||||
ZENHUB_API_TOKEN="..."
|
||||
```
|
||||
- The `cypress-bot` GitHub app credentials are also needed. Ask another team member who has done a deploy for those.
|
||||
```text
|
||||
GITHUB_APP_CYPRESS_INSTALLATION_ID=
|
||||
GITHUB_APP_ID=
|
||||
GITHUB_PRIVATE_KEY=
|
||||
```
|
||||
- For purging the Cloudflare cache (part of the `move-binaries` step), you'll need `CF_ZONEID` and `CF_TOKEN` set. These can be found in 1Password. If you don't have access, ask a team member who has done a deploy.
|
||||
```text
|
||||
CF_ZONEID="..."
|
||||
CF_TOKEN="..."
|
||||
```
|
||||
- Tip: Use [as-a](https://github.com/bahmutov/as-a) to manage environment variables for different situations.
|
||||
|
||||
```text
|
||||
ci_json='{"githubToken":"...","circleToken":"..."}'
|
||||
```
|
||||
|
||||
- You'll also need to put the GitHub token under its own variable and get a [ZenHub API token](https://app.zenhub.com/dashboard/tokens) for the `release-automations` step.
|
||||
|
||||
```text
|
||||
GITHUB_TOKEN="..."
|
||||
ZENHUB_API_TOKEN="..."
|
||||
```
|
||||
|
||||
- The `cypress-bot` GitHub app credentials are also needed. Ask another team member who has done a deploy for those.
|
||||
|
||||
```text
|
||||
GITHUB_APP_CYPRESS_INSTALLATION_ID=
|
||||
GITHUB_APP_ID=
|
||||
GITHUB_PRIVATE_KEY=
|
||||
```
|
||||
|
||||
- For purging the Cloudflare cache (part of the `move-binaries` step), you'll need `CF_ZONEID` and `CF_TOKEN` set. These can be found in 1Password. If you don't have access, ask a team member who has done a deploy.
|
||||
|
||||
```text
|
||||
CF_ZONEID="..."
|
||||
CF_TOKEN="..."
|
||||
```
|
||||
|
||||
- Tip: Use [as-a](https://github.com/bahmutov/as-a) to manage environment variables for different situations.
|
||||
|
||||
### Before Publishing a New Version
|
||||
|
||||
@@ -68,6 +78,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
|
||||
2. If there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version, update the corresponding dependency in [`packages/example`](../packages/example) to that new version.
|
||||
|
||||
3. Use the `move-binaries` script to move the binaries for `<commit sha>` from `beta` to the `desktop` folder for `<new target version>`. This also purges the cloudflare cache for this version.
|
||||
|
||||
```shell
|
||||
yarn move-binaries --sha <commit sha> --version <new target version>
|
||||
```
|
||||
@@ -79,11 +90,13 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
|
||||
2. Scroll down past the changes to the comments. The first comment should be a `cypress-bot` comment that includes a line beginning `npm install ...`. Grab the `https://cdn.../npm/X.Y.Z/<long sha>/cypress.tgz` link.
|
||||

|
||||
- Publish to the npm registry straight from the URL:
|
||||
|
||||
```shell
|
||||
npm publish https://cdn.cypress.io/beta/npm/X.Y.Z/<long sha>/cypress.tgz --tag dev
|
||||
```
|
||||
|
||||
5. Double-check that the new version has been published under the `dev` tag using `npm info cypress` or [available-versions](https://github.com/bahmutov/available-versions). `latest` should still point to the previous version. Example output:
|
||||
|
||||
```shell
|
||||
dist-tags:
|
||||
dev: 3.4.0 latest: 3.3.2
|
||||
@@ -96,31 +109,36 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
|
||||
- Go into a project, run a quick test, make sure things look right
|
||||
- Optionally, do more thorough tests:
|
||||
- Trigger test projects from the command line (if you have the appropriate permissions)
|
||||
```
|
||||
|
||||
```shell
|
||||
node scripts/test-other-projects.js --npm cypress@X.Y.Z --binary X.Y.Z
|
||||
```
|
||||
- Test the new version of Cypress against the Cypress dashboard repo.
|
||||
|
||||
- Test the new version of Cypress against the Cypress dashboard repo.
|
||||
|
||||
7. Confirm that every issue labeled [stage: pending release](https://github.com/cypress-io/cypress/issues?q=label%3A%22stage%3A+pending+release%22+is%3Aclosed) has a ZenHub release set. **Tip:** there is a command in [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to list and check such issues. Without a ZenHub release issues will not be included in the right changelog.
|
||||
|
||||
8. Deploy the release-specific documentation and changelog in [cypress-documentation](https://github.com/cypress-io/cypress-documentation).
|
||||
- If there is not already a release-specific PR open, create one. You can use [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to generate a starting point for the changelog, based off of ZenHub:
|
||||
```
|
||||
|
||||
```shell
|
||||
cd packages/issues-in-release
|
||||
yarn do:changelog --release <release label>
|
||||
```
|
||||
|
||||
- Ensure the changelog is up-to-date and has the correct date.
|
||||
- Merge any release-specific documentation changes into the main release PR.
|
||||
- You can view the doc's [branch deploy preview](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md#pull-requests) by clicking 'Details' on the PR's `netlify-cypress-docs/deploy-preview` GitHub status check.
|
||||
- Merge this PR into `master` to deploy it to production.
|
||||
|
||||
9. Make the new npm version the "latest" version by updating the dist-tag `latest` to point to the new version:
|
||||
9. Make the new npm version the "latest" version by updating the dist-tag `latest` to point to the new version:
|
||||
|
||||
```shell
|
||||
npm dist-tag add cypress@X.Y.Z
|
||||
```
|
||||
|
||||
10. Run `binary-release` to update the [download server's manifest](https://download.cypress.io/desktop.json). This will also ensure the binary for the version is downloadable for each system.
|
||||
|
||||
```shell
|
||||
yarn binary-release --version X.Y.Z
|
||||
```
|
||||
@@ -135,6 +153,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
|
||||
- Move all issues that are still open from the current release to the appropriate future release.
|
||||
|
||||
14. Bump `version` in [`package.json`](package.json), commit it to `develop`, tag it with the version, and push the tag up:
|
||||
|
||||
```shell
|
||||
git commit -am "release X.Y.Z [skip ci]"
|
||||
git log --pretty=oneline
|
||||
@@ -142,7 +161,9 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
|
||||
git tag -a vX.Y.Z <sha>
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
15. Merge `develop` into `master` and push both branches up. Note: pushing to `master` will automatically publish any independent npm packages that have not yet been published.
|
||||
|
||||
```shell
|
||||
git push origin develop
|
||||
git checkout master
|
||||
@@ -152,16 +173,20 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
|
||||
|
||||
16. Inside of [cypress-io/release-automations][release-automations]:
|
||||
- Publish GitHub release to [cypress-io/cypress/releases](https://github.com/cypress-io/cypress/releases) using package `set-releases`:
|
||||
|
||||
```shell
|
||||
cd packages/set-releases && npm run release-log -- --version X.Y.Z
|
||||
```
|
||||
|
||||
- Add a comment to each GH issue that has been resolved with the new published version using package `issues-in-release`:
|
||||
|
||||
```shell
|
||||
cd packages/issues-in-release && npm run do:comment -- --release X.Y.Z
|
||||
```
|
||||
|
||||
- Confirm there are no issues with the label [stage: pending release](https://github.com/cypress-io/cypress/issues?q=label%3A%22stage%3A+pending+release%22+is%3Aclosed) left
|
||||
|
||||
17. Publish a new docker image in [`cypress-docker-images`](https://github.com/cypress-io/cypress-docker-images) under `included` for the new cypress version. Note: we use the base image with the Node version matching the bundled Node version.
|
||||
17. Publish a new docker image in [`cypress-docker-images`](https://github.com/cypress-io/cypress-docker-images) under `included` for the new cypress version. Note: we use the base image with the Node version matching the bundled Node version. Instructions for updating `cypress-docker-images` can be found [here](https://github.com/cypress-io/cypress-docker-images/blob/master/CONTRIBUTING.md#add-new-included-image).
|
||||
|
||||
18. Update example projects to the new version. For most projects, 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)
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
# [create-cypress-tests-v1.3.0](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.2.0...create-cypress-tests-v1.3.0) (2021-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Restore broken gif ([#18987](https://github.com/cypress-io/cypress/issues/18987)) ([f251681](https://github.com/cypress-io/cypress/commit/f251681b814b102ca374abdef148b777c4e72c67))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
|
||||
|
||||
# [create-cypress-tests-v1.2.0](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v1.1.3...create-cypress-tests-v1.2.0) (2021-11-10)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# [@cypress/eslint-plugin-dev-v5.2.0](https://github.com/cypress-io/cypress/compare/@cypress/eslint-plugin-dev-v5.1.0...@cypress/eslint-plugin-dev-v5.2.0) (2021-12-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
|
||||
|
||||
# [@cypress/eslint-plugin-dev-v5.1.0](https://github.com/cypress-io/cypress/compare/@cypress/eslint-plugin-dev-v5.0.2...@cypress/eslint-plugin-dev-v5.1.0) (2021-02-16)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
# [@cypress/react-v5.11.0](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.10.3...@cypress/react-v5.11.0) (2021-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **react:** link to rerender example ([#19020](https://github.com/cypress-io/cypress/issues/19020)) ([2a471d6](https://github.com/cypress-io/cypress/commit/2a471d633a7cf5bd94cfa7d876ddb27cc32626d1))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
|
||||
|
||||
# [@cypress/react-v5.10.3](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.10.2...@cypress/react-v5.10.3) (2021-11-01)
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"@types/semver": "7.3.4",
|
||||
"arg": "4.1.3",
|
||||
"autoprefixer": "9.7.6",
|
||||
"axios": "0.18.1",
|
||||
"axios": "0.21.2",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-istanbul": "6.0.0",
|
||||
"babel-plugin-module-resolver": "4.0.0",
|
||||
|
||||
@@ -5,6 +5,7 @@ const { allowModuleSourceInPlace } = require('../utils/webpack-helpers')
|
||||
const { addCypressToWebpackEslintRulesInPlace } = require('../utils/eslint-helpers')
|
||||
const { getTranspileFolders } = require('../utils/get-transpile-folders')
|
||||
const { addFolderToBabelLoaderTranspileInPlace } = require('../utils/babel-helpers')
|
||||
const { reactScriptsFiveModifications, isReactScripts5 } = require('../../dist/react-scripts/reactScriptsFive')
|
||||
|
||||
module.exports = function findReactScriptsWebpackConfig (config, {
|
||||
webpackConfigPath,
|
||||
@@ -28,6 +29,12 @@ module.exports = function findReactScriptsWebpackConfig (config, {
|
||||
addFolderToBabelLoaderTranspileInPlace(cypressFolder, webpackConfig)
|
||||
})
|
||||
|
||||
if (isReactScripts5) {
|
||||
debug('Modifying configuration for react-scripts@5')
|
||||
|
||||
reactScriptsFiveModifications(webpackConfig)
|
||||
}
|
||||
|
||||
debug('resolved webpack config: %o', webpackConfig)
|
||||
|
||||
return webpackConfig
|
||||
|
||||
61
npm/react/plugins/react-scripts/reactScriptsFive.ts
Normal file
61
npm/react/plugins/react-scripts/reactScriptsFive.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import _debug from 'debug'
|
||||
import type { Configuration } from 'webpack'
|
||||
import reactScriptsPackageJson from 'react-scripts/package.json'
|
||||
|
||||
const debug = _debug('@cypress/react:react-scripts')
|
||||
|
||||
type DefinePlugin =
|
||||
| { definitions: Record<string, Record<string, any>> }
|
||||
| undefined;
|
||||
type ESLintWebpackPlugin =
|
||||
| { options: { baseConfig?: { globals?: Record<string, any> } } }
|
||||
| undefined;
|
||||
|
||||
export function reactScriptsFiveModifications (webpackConfig: Configuration) {
|
||||
// React-Scripts sets the webpack target to ["browserslist"] which tells
|
||||
// webpack to target the browsers found within the browserslist config
|
||||
// depending on the environment (process.env.NODE_ENV). Since we set
|
||||
// process.env.NODE_ENV = "test", webpack is unable to find any browsers and errors.
|
||||
// We set BROWSERSLIST_ENV = "development" to override the default NODE_ENV search of browsers.
|
||||
if (!process.env.BROWSERSLIST_ENV) {
|
||||
process.env.BROWSERSLIST_ENV = 'development'
|
||||
}
|
||||
|
||||
// We use the "development" configuration of the react-scripts webpack config.
|
||||
// There is a conflict when settings process.env.NODE_ENV = "test" since DefinePlugin
|
||||
// uses the "development" configuration and expects process.env.NODE_ENV = "development".
|
||||
const definePlugin: DefinePlugin = webpackConfig.plugins?.find(
|
||||
(plugin) => plugin.constructor.name === 'DefinePlugin'
|
||||
) as unknown as DefinePlugin
|
||||
|
||||
if (definePlugin) {
|
||||
const processEnv = definePlugin.definitions['process.env']
|
||||
|
||||
processEnv.NODE_ENV = JSON.stringify('development')
|
||||
|
||||
debug('Found "DefinePlugin", modified "process.env" definition %o', processEnv)
|
||||
}
|
||||
|
||||
// React-Scripts v5 no longers uses a loader to configure eslint, so we add globals
|
||||
// to the plugin.
|
||||
const eslintPlugin = webpackConfig.plugins?.find(
|
||||
(plugin) => plugin.constructor.name === 'ESLintWebpackPlugin'
|
||||
) as unknown as ESLintWebpackPlugin
|
||||
|
||||
if (eslintPlugin) {
|
||||
const cypressGlobals = ['cy', 'Cypress', 'before', 'after', 'context']
|
||||
.reduce((acc, global) => ({ ...acc, [global]: 'writable' }), {})
|
||||
|
||||
eslintPlugin.options.baseConfig = {
|
||||
...eslintPlugin.options.baseConfig,
|
||||
globals: {
|
||||
...eslintPlugin.options.baseConfig?.globals,
|
||||
...cypressGlobals,
|
||||
},
|
||||
}
|
||||
|
||||
debug('Found ESLintWebpackPlugin, modified eslint config %o', eslintPlugin.options.baseConfig)
|
||||
}
|
||||
}
|
||||
|
||||
export const isReactScripts5 = Number(reactScriptsPackageJson.version[0]) >= 5
|
||||
@@ -19,7 +19,8 @@
|
||||
"cypress"
|
||||
] /* Type declaration files to be included in compilation. */,
|
||||
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# [@cypress/vite-dev-server-v2.2.2](https://github.com/cypress-io/cypress/compare/@cypress/vite-dev-server-v2.2.1...@cypress/vite-dev-server-v2.2.2) (2021-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check the port is avail on all local hosts ([#19402](https://github.com/cypress-io/cypress/issues/19402)) ([4826175](https://github.com/cypress-io/cypress/commit/4826175040bfc024a36df47dc0c74f2871fa944f))
|
||||
|
||||
# [@cypress/vite-dev-server-v2.2.1](https://github.com/cypress-io/cypress/compare/@cypress/vite-dev-server-v2.2.0...@cypress/vite-dev-server-v2.2.1) (2021-11-19)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolve, posix, sep } from 'path'
|
||||
import { resolve, sep } from 'path'
|
||||
import { readFile } from 'fs'
|
||||
import { promisify } from 'util'
|
||||
import Debug from 'debug'
|
||||
|
||||
@@ -47,7 +47,7 @@ const resolveServerConfig = async ({ viteConfig, options }: StartDevServerOption
|
||||
|
||||
finalConfig.server = finalConfig.server || {}
|
||||
|
||||
finalConfig.server.port = await getPort({ port: finalConfig.server.port || 3000, host: 'localhost' }),
|
||||
finalConfig.server.port = await getPort({ port: finalConfig.server.port || 3000 }),
|
||||
|
||||
// Ask vite to pre-optimize all dependencies of the specs
|
||||
finalConfig.optimizeDeps = finalConfig.optimizeDeps || {}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# [@cypress/vue-v3.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vue-v3.0.5...@cypress/vue-v3.1.0) (2021-12-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
|
||||
|
||||
# [@cypress/vue-v3.0.5](https://github.com/cypress-io/cypress/compare/@cypress/vue-v3.0.4...@cypress/vue-v3.0.5) (2021-11-10)
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@vue/cli-plugin-babel": "~4.4.0",
|
||||
"@vue/cli-service": "~4.4.0",
|
||||
"@vue/compiler-sfc": "3.1.5",
|
||||
"axios": "0.19.2",
|
||||
"axios": "0.21.2",
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-plugin-istanbul": "^6.0.0",
|
||||
"babel-preset-typescript-vue3": "^2.0.14",
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# [@cypress/webpack-dev-server-v1.8.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v1.7.0...@cypress/webpack-dev-server-v1.8.0) (2021-12-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
|
||||
|
||||
# [@cypress/webpack-dev-server-v1.7.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v1.6.0...@cypress/webpack-dev-server-v1.7.0) (2021-10-15)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# [@cypress/webpack-preprocessor-v5.11.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-preprocessor-v5.10.0...@cypress/webpack-preprocessor-v5.11.0) (2021-12-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
|
||||
|
||||
# [@cypress/webpack-preprocessor-v5.10.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-preprocessor-v5.9.1...@cypress/webpack-preprocessor-v5.10.0) (2021-11-10)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cypress",
|
||||
"version": "9.1.1",
|
||||
"version": "9.2.0",
|
||||
"description": "Cypress.io end to end testing tool",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -23,6 +23,7 @@ exports['src/index .getDefaultValues returns list of public config keys 1'] = {
|
||||
"e2e": {},
|
||||
"env": {},
|
||||
"execTimeout": 60000,
|
||||
"exit": true,
|
||||
"experimentalFetchPolyfill": false,
|
||||
"experimentalInteractiveRunEvents": false,
|
||||
"experimentalSessionSupport": false,
|
||||
@@ -33,6 +34,8 @@ exports['src/index .getDefaultValues returns list of public config keys 1'] = {
|
||||
"ignoreTestFiles": "*.hot-update.js",
|
||||
"includeShadowDom": false,
|
||||
"integrationFolder": "cypress/integration",
|
||||
"isInteractive": true,
|
||||
"keystrokeDelay": 0,
|
||||
"modifyObstructiveCode": true,
|
||||
"numTestsKeptInMemory": 50,
|
||||
"pageLoadTimeout": 60000,
|
||||
@@ -97,6 +100,7 @@ exports['src/index .getPublicConfigKeys returns list of public config keys 1'] =
|
||||
"e2e",
|
||||
"env",
|
||||
"execTimeout",
|
||||
"exit",
|
||||
"experimentalFetchPolyfill",
|
||||
"experimentalInteractiveRunEvents",
|
||||
"experimentalSessionSupport",
|
||||
@@ -107,6 +111,7 @@ exports['src/index .getPublicConfigKeys returns list of public config keys 1'] =
|
||||
"ignoreTestFiles",
|
||||
"includeShadowDom",
|
||||
"integrationFolder",
|
||||
"keystrokeDelay",
|
||||
"modifyObstructiveCode",
|
||||
"nodeVersion",
|
||||
"numTestsKeptInMemory",
|
||||
@@ -142,5 +147,6 @@ exports['src/index .getPublicConfigKeys returns list of public config keys 1'] =
|
||||
"watchForFileChanges",
|
||||
"browsers",
|
||||
"hosts",
|
||||
"isInteractive",
|
||||
"modifyObstructiveCode"
|
||||
]
|
||||
|
||||
@@ -154,4 +154,4 @@ Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\
|
||||
|
||||
exports['empty string'] = `
|
||||
Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\`
|
||||
`
|
||||
`
|
||||
@@ -20,6 +20,7 @@ const breakingKeys = _.map(breakingOptions, 'name')
|
||||
const defaultValues = createIndex(options, 'name', 'defaultValue')
|
||||
const publicConfigKeys = _(options).reject({ isInternal: true }).map('name').value()
|
||||
const validationRules = createIndex(options, 'name', 'validation')
|
||||
const testConfigOverrideOptions = createIndex(options, 'name', 'canUpdateDuringTestTime')
|
||||
|
||||
module.exports = {
|
||||
allowed: (obj = {}) => {
|
||||
@@ -101,4 +102,16 @@ module.exports = {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
validateNoReadOnlyConfig: (config, onErr) => {
|
||||
let errProperty
|
||||
|
||||
Object.keys(config).some((option) => {
|
||||
return errProperty = testConfigOverrideOptions[option] === false ? option : undefined
|
||||
})
|
||||
|
||||
if (errProperty) {
|
||||
return onErr(errProperty)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ interface ResolvedConfigOption {
|
||||
validation: Function
|
||||
isFolder?: boolean
|
||||
isExperimental?: boolean
|
||||
/**
|
||||
* Can be mutated with Cypress.config() or test-specific configuration overrides
|
||||
*/
|
||||
canUpdateDuringTestTime?: boolean
|
||||
}
|
||||
|
||||
interface RuntimeConfigOption {
|
||||
@@ -13,6 +17,10 @@ interface RuntimeConfigOption {
|
||||
defaultValue: any
|
||||
validation: Function
|
||||
isInternal?: boolean
|
||||
/**
|
||||
* Can be mutated with Cypress.config() or test-specific configuration overrides
|
||||
*/
|
||||
canUpdateDuringTestTime?: boolean
|
||||
}
|
||||
|
||||
interface BreakingOption {
|
||||
@@ -72,158 +80,204 @@ const resolvedOptions: Array<ResolvedConfigOption> = [
|
||||
name: 'animationDistanceThreshold',
|
||||
defaultValue: 5,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'baseUrl',
|
||||
defaultValue: null,
|
||||
validation: validate.isFullyQualifiedUrl,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'blockHosts',
|
||||
defaultValue: null,
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'chromeWebSecurity',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'clientCertificates',
|
||||
defaultValue: [],
|
||||
validation: validate.isValidClientCertificatesSet,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'component',
|
||||
// runner-ct overrides
|
||||
defaultValue: {},
|
||||
validation: isValidConfig,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'componentFolder',
|
||||
defaultValue: 'cypress/component',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'defaultCommandTimeout',
|
||||
defaultValue: 4000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'downloadsFolder',
|
||||
defaultValue: 'cypress/downloads',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'e2e',
|
||||
// e2e runner overrides
|
||||
defaultValue: {},
|
||||
validation: isValidConfig,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'env',
|
||||
defaultValue: {},
|
||||
validation: validate.isPlainObject,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'execTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'exit',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalFetchPolyfill',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalInteractiveRunEvents',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalSessionSupport',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'experimentalSourceRewriting',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalStudio',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'fileServerFolder',
|
||||
defaultValue: '',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'fixturesFolder',
|
||||
defaultValue: 'cypress/fixtures',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'ignoreTestFiles',
|
||||
defaultValue: '*.hot-update.js',
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'includeShadowDom',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'integrationFolder',
|
||||
defaultValue: 'cypress/integration',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'keystrokeDelay',
|
||||
defaultValue: 0,
|
||||
validation: validate.isNumberOrFalse,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'modifyObstructiveCode',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'nodeVersion',
|
||||
validation: validate.isOneOf('bundled', 'system'),
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'numTestsKeptInMemory',
|
||||
defaultValue: 50,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'pageLoadTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'pluginsFile',
|
||||
defaultValue: 'cypress/plugins',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'port',
|
||||
defaultValue: null,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'projectId',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'redirectionLimit',
|
||||
defaultValue: 20,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'reporter',
|
||||
defaultValue: 'spec',
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'reporterOptions',
|
||||
defaultValue: null,
|
||||
validation: validate.isPlainObject,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'requestTimeout',
|
||||
defaultValue: 5000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'resolvedNodePath',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'resolvedNodeVersion',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'responseTimeout',
|
||||
defaultValue: 30000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'retries',
|
||||
defaultValue: {
|
||||
@@ -231,82 +285,101 @@ const resolvedOptions: Array<ResolvedConfigOption> = [
|
||||
openMode: 0,
|
||||
},
|
||||
validation: validate.isValidRetriesConfig,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'screenshotOnRunFailure',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'screenshotsFolder',
|
||||
defaultValue: 'cypress/screenshots',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'slowTestThreshold',
|
||||
defaultValue: (options: Record<string, any> = {}) => options.testingType === 'component' ? 250 : 10000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'scrollBehavior',
|
||||
defaultValue: 'top',
|
||||
validation: validate.isOneOf('center', 'top', 'bottom', 'nearest', false),
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'supportFile',
|
||||
defaultValue: 'cypress/support',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'supportFolder',
|
||||
defaultValue: false,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'taskTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'testFiles',
|
||||
defaultValue: '**/*.*',
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'trashAssetsBeforeRuns',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'userAgent',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'video',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'videoCompression',
|
||||
defaultValue: 32,
|
||||
validation: validate.isNumberOrFalse,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'videosFolder',
|
||||
defaultValue: 'cypress/videos',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'videoUploadOnPasses',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'viewportHeight',
|
||||
defaultValue: 660,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'viewportWidth',
|
||||
defaultValue: 1000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'waitForAnimations',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'watchForFileChanges',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -316,15 +389,18 @@ const runtimeOptions: Array<RuntimeConfigOption> = [
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'browsers',
|
||||
defaultValue: [],
|
||||
validation: validate.isValidBrowserList,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'clientRoute',
|
||||
defaultValue: '/__/',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'configFile',
|
||||
defaultValue: 'cypress.json',
|
||||
@@ -332,59 +408,76 @@ const runtimeOptions: Array<RuntimeConfigOption> = [
|
||||
// not truly internal, but can only be set via cli,
|
||||
// so we don't consider it a "public" option
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'devServerPublicPathRoute',
|
||||
defaultValue: '/__cypress/src',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'hosts',
|
||||
defaultValue: null,
|
||||
validation: validate.isPlainObject,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'isInteractive',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'isTextTerminal',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'morgan',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'modifyObstructiveCode',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'namespace',
|
||||
defaultValue: '__cypress',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'reporterRoute',
|
||||
defaultValue: '/__cypress/reporter',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'socketId',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'socketIoCookie',
|
||||
defaultValue: '__socket.io',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'socketIoRoute',
|
||||
defaultValue: '/__socket.io',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'xhrRoute',
|
||||
defaultValue: '/xhrs/',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ describe('src/index', () => {
|
||||
'baseUrl': 'https://',
|
||||
}, errorFn)
|
||||
|
||||
expect(errorFn).to.have.been.callCount(0)
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
|
||||
it('calls error callback if config is invalid', () => {
|
||||
@@ -125,7 +125,7 @@ describe('src/index', () => {
|
||||
configFile: 'config.js',
|
||||
})
|
||||
|
||||
expect(errorFn).to.have.been.callCount(0)
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
|
||||
it('calls error callback if config contains breaking option that should throw an error', () => {
|
||||
@@ -146,4 +146,31 @@ describe('src/index', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.validateNoReadOnlyConfig', () => {
|
||||
it('returns an error if validation fails', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoReadOnlyConfig({ chromeWebSecurity: false }, errorFn)
|
||||
|
||||
expect(errorFn).to.have.callCount(1)
|
||||
expect(errorFn).to.have.been.calledWithMatch(/chromeWebSecurity/)
|
||||
})
|
||||
|
||||
it('does not return an error if validation succeeds', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoReadOnlyConfig({ requestTimeout: 1000 }, errorFn)
|
||||
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
|
||||
it('does not return an error if configuration is a non-Cypress config option', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoReadOnlyConfig({ foo: 'bar' }, errorFn)
|
||||
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,5 +17,25 @@
|
||||
h1 { font-size: 32px }
|
||||
</style>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
// https://github.com/cypress-io/cypress/issues/19377
|
||||
Below is added to ensure `style`, `script` tags are not removed by `cy.contains()` command.
|
||||
-->
|
||||
<p>Hello</p>
|
||||
<button id="my_button_1" style="display: none;">I'm a button</button>
|
||||
<button id="my_button_2">Click me</button>
|
||||
<div id="result"></div>
|
||||
<style>
|
||||
button#my_button_1 {
|
||||
background-color: red;
|
||||
display: inline !important;
|
||||
}
|
||||
</style>
|
||||
<script id="my_script">
|
||||
document.getElementById('my_button_2').addEventListener('click', function() {
|
||||
document.getElementById('result').innerHTML = 'This is the result';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
38
packages/driver/cypress/fixtures/issue-17512.html
Normal file
38
packages/driver/cypress/fixtures/issue-17512.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
function defaultBehavior(e) {
|
||||
document.getElementById('result').innerHTML = JSON.stringify(e.target.getAttribute('target'))
|
||||
}
|
||||
|
||||
function setAttributeBehavior(e) {
|
||||
e.target.setAttribute('target', '_top')
|
||||
document.getElementById('result2').innerHTML = JSON.stringify(e.target.getAttribute('target'))
|
||||
}
|
||||
|
||||
function removeAttributeBehavior(e) {
|
||||
e.target.setAttribute('target', '_top')
|
||||
e.target.removeAttribute('target')
|
||||
document.getElementById('result3').innerHTML = JSON.stringify(e.target.getAttribute('target'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<a href="#" id="link" onclick="defaultBehavior(event)">link 1</a>
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="#" id="link2" onclick="setAttributeBehavior(event)">link 2</a>
|
||||
<div id="result2"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="#" id="link3" onclick="removeAttributeBehavior(event)">link 3</a>
|
||||
<div id="result3"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,39 @@
|
||||
const { assertLogLength } = require('../../support/utils')
|
||||
const { $, _ } = Cypress
|
||||
|
||||
const captureCommands = () => {
|
||||
const commands = []
|
||||
|
||||
let current
|
||||
|
||||
cy.on('command:start', (command) => {
|
||||
current = command
|
||||
commands.push({
|
||||
name: command.attributes.name,
|
||||
snapshots: 0,
|
||||
retries: 0,
|
||||
})
|
||||
})
|
||||
|
||||
cy.on('command:retry', () => {
|
||||
commands[commands.length - 1].retries++
|
||||
})
|
||||
|
||||
cy.on('snapshot', () => {
|
||||
// Snapshots can occur outside the context of a command - for example, `expect(foo).to.exist` without any wrapping cy command.
|
||||
// So we keep track of the current command when one starts, and if we're not inside that, create an 'empty' command
|
||||
// for the snapshot to belong to
|
||||
if (!commands.length || current !== cy.state('current')) {
|
||||
current = null
|
||||
commands.push({ name: null, snapshots: 0, retries: 0 })
|
||||
}
|
||||
|
||||
commands[commands.length - 1].snapshots++
|
||||
})
|
||||
|
||||
return () => _.cloneDeep(commands)
|
||||
}
|
||||
|
||||
describe('src/cy/commands/assertions', () => {
|
||||
before(() => {
|
||||
cy
|
||||
@@ -10,10 +43,14 @@ describe('src/cy/commands/assertions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
let testCommands
|
||||
|
||||
beforeEach(function () {
|
||||
const doc = cy.state('document')
|
||||
|
||||
$(doc.body).empty().html(this.body)
|
||||
|
||||
testCommands = captureCommands()
|
||||
})
|
||||
|
||||
context('#should', () => {
|
||||
@@ -29,8 +66,14 @@ describe('src/cy/commands/assertions', () => {
|
||||
})
|
||||
|
||||
it('returns the subject for chainability', () => {
|
||||
cy.noop({ foo: 'bar' }).should('deep.eq', { foo: 'bar' }).then((obj) => {
|
||||
expect(obj).to.deep.eq({ foo: 'bar' })
|
||||
cy
|
||||
.noop({ foo: 'bar' }).should('deep.eq', { foo: 'bar' })
|
||||
.then((obj) => {
|
||||
expect(testCommands()).to.eql([
|
||||
{ name: 'noop', snapshots: 0, retries: 0 },
|
||||
{ name: 'should', snapshots: 1, retries: 0 },
|
||||
{ name: 'then', snapshots: 0, retries: 0 },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,11 +164,19 @@ describe('src/cy/commands/assertions', () => {
|
||||
cy.wrap(obj).then(() => {
|
||||
setTimeout(() => {
|
||||
obj.foo = 'baz'
|
||||
}
|
||||
, 100)
|
||||
}, 100)
|
||||
|
||||
cy.wrap(obj)
|
||||
}).should('deep.eq', { foo: 'baz' })
|
||||
})
|
||||
.should('deep.eq', { foo: 'baz' })
|
||||
.then(() => {
|
||||
expect(testCommands()).to.containSubset([
|
||||
{ name: 'wrap', snapshots: 1, retries: 0 },
|
||||
{ name: 'then', snapshots: 0, retries: 0 },
|
||||
{ name: 'wrap', snapshots: 2, retries: (r) => r > 1 },
|
||||
{ name: 'then', snapshots: 0, retries: 0 },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/16006
|
||||
@@ -151,6 +202,18 @@ describe('src/cy/commands/assertions', () => {
|
||||
cy.get('button:first').should(($button) => {
|
||||
expect($button).to.have.class('ready')
|
||||
})
|
||||
.then(() => {
|
||||
expect(testCommands()).to.eql([
|
||||
// cy.get() has 2 snapshots, 1 for itself, and 1
|
||||
// for the .should(...) assertion.
|
||||
|
||||
// TODO: Investigate whether or not the 2 commands are
|
||||
// snapshotted at the same time. If there's no tick between
|
||||
// them, we could reuse the snapshots
|
||||
{ name: 'get', snapshots: 2, retries: 2 },
|
||||
{ name: 'then', snapshots: 0, retries: 0 },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('works with regular objects', () => {
|
||||
@@ -1475,7 +1538,7 @@ describe('src/cy/commands/assertions', () => {
|
||||
done()
|
||||
})
|
||||
|
||||
cy.get('div').should(($divs) => {
|
||||
cy.get('div', { timeout: 100 }).should(($divs) => {
|
||||
expect($divs, 'Filter should have 1 items').to.have.length(1)
|
||||
})
|
||||
})
|
||||
@@ -2927,4 +2990,42 @@ describe('src/cy/commands/assertions', () => {
|
||||
cy.get('.foo').should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
context('implicit assertions', () => {
|
||||
// https://github.com/cypress-io/cypress/issues/18549
|
||||
// A targeted test for the above issue - in the absence of retries, only a single snapshot
|
||||
// should be taken.
|
||||
it('only snapshots once when failing to find DOM elements and not retrying', (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(testCommands()).to.eql([{
|
||||
name: 'get',
|
||||
snapshots: 1,
|
||||
retries: 0,
|
||||
}])
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
cy.get('.badId', { timeout: 0 })
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/18549
|
||||
// This issue was also causing two DOM snapshots to be taken every 50ms
|
||||
// while waiting for an element to exist. The first test is sufficient to
|
||||
// prevent regressions of the specific issue, but this one is intended to
|
||||
// more generally assert that retries do not trigger multiple snapshots.
|
||||
it('only snapshots once when retrying assertions', (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(testCommands()).to.containSubset([{
|
||||
name: 'get',
|
||||
snapshots: 1,
|
||||
retries: (v) => v > 1,
|
||||
}])
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
cy.get('.badId', { timeout: 1000 })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -101,9 +101,7 @@ describe('src/cy/commands/fixtures', () => {
|
||||
return null
|
||||
})
|
||||
|
||||
it('throws if fixturesFolder is set to false', {
|
||||
fixturesFolder: false,
|
||||
}, function (done) {
|
||||
it('throws if fixturesFolder is set to false', { fixturesFolder: false }, function (done) {
|
||||
cy.on('fail', () => {
|
||||
const { lastLog } = this
|
||||
|
||||
|
||||
@@ -315,6 +315,59 @@ describe('src/cy/commands/navigation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('handles hashchange events', () => {
|
||||
const emit = cy.spy(Cypress, 'emit').log(false).withArgs('url:changed')
|
||||
|
||||
cy
|
||||
.visit('/fixtures/generic.html')
|
||||
.get('#hashchange').click()
|
||||
.then(() => {
|
||||
cy.go('back')
|
||||
cy.go('forward')
|
||||
cy.get('#dimensions').click()
|
||||
cy.go('back')
|
||||
cy.go('back')
|
||||
})
|
||||
.then(function () {
|
||||
expect(emit.firstCall).to.be.calledWith(
|
||||
'url:changed',
|
||||
'http://localhost:3500/fixtures/generic.html',
|
||||
)
|
||||
|
||||
expect(emit.secondCall).to.be.calledWith(
|
||||
'url:changed',
|
||||
'http://localhost:3500/fixtures/generic.html#hashchange',
|
||||
)
|
||||
|
||||
expect(emit.thirdCall).to.be.calledWith(
|
||||
'url:changed',
|
||||
'http://localhost:3500/fixtures/generic.html',
|
||||
)
|
||||
|
||||
expect(emit.getCall(3)).to.be.calledWith(
|
||||
'url:changed',
|
||||
'http://localhost:3500/fixtures/generic.html#hashchange',
|
||||
)
|
||||
|
||||
expect(emit.getCall(4)).to.be.calledWith(
|
||||
'url:changed',
|
||||
'http://localhost:3500/fixtures/dimensions.html',
|
||||
)
|
||||
|
||||
expect(emit.getCall(5)).to.be.calledWith(
|
||||
'url:changed',
|
||||
'http://localhost:3500/fixtures/generic.html#hashchange',
|
||||
)
|
||||
|
||||
expect(emit.getCall(6)).to.be.calledWith(
|
||||
'url:changed',
|
||||
'http://localhost:3500/fixtures/generic.html',
|
||||
)
|
||||
|
||||
expect(emit.callCount).to.eq(7)
|
||||
})
|
||||
})
|
||||
|
||||
it('removes listeners', () => {
|
||||
cy
|
||||
.visit('/fixtures/generic.html')
|
||||
@@ -1440,30 +1493,6 @@ describe('src/cy/commands/navigation', () => {
|
||||
})
|
||||
|
||||
it('throws attempting to visit 2 unique ip addresses', function (done) {
|
||||
const $autIframe = cy.state('$autIframe')
|
||||
|
||||
const load = () => {
|
||||
return $autIframe.trigger('load')
|
||||
}
|
||||
|
||||
cy.stub(Cypress, 'backend')
|
||||
.withArgs('resolve:url')
|
||||
.resolves({
|
||||
isOkStatusCode: true,
|
||||
isHtml: true,
|
||||
url: 'http://127.0.0.1:3500',
|
||||
})
|
||||
|
||||
// whenever we're told to change the src
|
||||
// just fire the load event directly on the $autIframe
|
||||
cy.stub(Cypress.utils, 'iframeSrc').callsFake(load)
|
||||
|
||||
// make it seem like we're already on http://127.0.0.1:3500
|
||||
const one = Cypress.Location.create('http://127.0.0.1:3500/fixtures/generic.html')
|
||||
|
||||
cy.stub(Cypress.utils, 'locExisting')
|
||||
.returns(one)
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
|
||||
@@ -2136,8 +2165,7 @@ describe('src/cy/commands/navigation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// this tests isLoading spinner
|
||||
// and page load event
|
||||
// this tests isLoading spinner and page load event
|
||||
context('#page loading', () => {
|
||||
beforeEach(function () {
|
||||
this.logs = []
|
||||
@@ -2453,6 +2481,105 @@ describe('src/cy/commands/navigation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/19230
|
||||
it('filters page load events when going back with window navigation', () => {
|
||||
const emit = cy.spy(Cypress, 'emit').log(false).withArgs('navigation:changed')
|
||||
|
||||
cy
|
||||
.visit('/fixtures/generic.html')
|
||||
.get('#hashchange').click()
|
||||
.window().then((win) => {
|
||||
return new Promise((resolve) => {
|
||||
cy.once('navigation:changed', resolve)
|
||||
|
||||
win.history.back()
|
||||
}).then(() => {
|
||||
return new Promise((resolve) => {
|
||||
cy.once('navigation:changed', resolve)
|
||||
|
||||
win.history.forward()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('#dimensions').click()
|
||||
.window().then((win) => {
|
||||
return new Promise((resolve) => {
|
||||
cy.on('navigation:changed', (event) => {
|
||||
if (event.includes('(load)')) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
win.history.back()
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve) => {
|
||||
cy.on('navigation:changed', resolve)
|
||||
win.history.back()
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
expect(emit.firstCall).to.be.calledWith(
|
||||
'navigation:changed',
|
||||
'page navigation event (load)',
|
||||
)
|
||||
|
||||
expect(emit.secondCall).to.be.calledWith(
|
||||
'navigation:changed',
|
||||
'page navigation event (before:load)',
|
||||
)
|
||||
|
||||
expect(emit.thirdCall).to.be.calledWith(
|
||||
'navigation:changed',
|
||||
'page navigation event (load)',
|
||||
)
|
||||
|
||||
expect(emit.getCall(3)).to.be.calledWithMatch(
|
||||
'navigation:changed',
|
||||
'hashchange',
|
||||
)
|
||||
|
||||
expect(emit.getCall(4)).to.be.calledWithMatch(
|
||||
'navigation:changed',
|
||||
'hashchange',
|
||||
)
|
||||
|
||||
expect(emit.getCall(5)).to.be.calledWithMatch(
|
||||
'navigation:changed',
|
||||
'hashchange',
|
||||
)
|
||||
|
||||
expect(emit.getCall(6)).to.be.calledWith(
|
||||
'navigation:changed',
|
||||
'page navigation event (before:load)',
|
||||
)
|
||||
|
||||
expect(emit.getCall(7)).to.be.calledWith(
|
||||
'navigation:changed',
|
||||
'page navigation event (load)',
|
||||
)
|
||||
|
||||
expect(emit.getCall(8)).to.be.calledWith(
|
||||
'navigation:changed',
|
||||
'page navigation event (before:load)',
|
||||
)
|
||||
|
||||
expect(emit.getCall(9)).to.be.calledWith(
|
||||
'navigation:changed',
|
||||
'page navigation event (load)',
|
||||
)
|
||||
|
||||
expect(emit.getCall(10)).to.be.calledWithMatch(
|
||||
'navigation:changed',
|
||||
'hashchange',
|
||||
)
|
||||
|
||||
expect(emit.callCount).to.eq(11)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('logs url changed event', () => {
|
||||
cy
|
||||
.visit('/fixtures/generic.html')
|
||||
|
||||
@@ -1319,6 +1319,61 @@ describe('network stubbing', function () {
|
||||
cy.contains('#result', '""').should('be.visible')
|
||||
})
|
||||
|
||||
// @see https://github.com/cypress-io/cypress/issues/19330
|
||||
// @see https://github.com/cypress-io/cypress/issues/19344
|
||||
it('load fixture as Buffer when encoding is null', function () {
|
||||
// call through normally on everything
|
||||
cy.spy(Cypress, 'backend')
|
||||
|
||||
cy.intercept('/fixtures/media/small.mp4', {
|
||||
fixture: 'media/small.mp4,null',
|
||||
}).as('video')
|
||||
|
||||
cy.visit('/fixtures/video.html')
|
||||
.then(() => {
|
||||
// @ts-ignore .getCall is a Sinon spy command
|
||||
expect(Cypress.backend.getCall(0)).to.be.calledWithMatch(
|
||||
'net',
|
||||
'route:added',
|
||||
{
|
||||
staticResponse: {
|
||||
fixture: {
|
||||
filePath: 'media/small.mp4',
|
||||
encoding: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('load fixture with specified encoding', function () {
|
||||
// call through normally on everything
|
||||
cy.spy(Cypress, 'backend')
|
||||
|
||||
cy.intercept('non-existing-image.png', {
|
||||
headers: { 'content-type': 'image/jpeg' },
|
||||
fixture: 'media/cypress.png,base64',
|
||||
}).as('video')
|
||||
|
||||
cy.visit('/fixtures/img-embed.html')
|
||||
.then(() => {
|
||||
// @ts-ignore .getCall is a Sinon spy command
|
||||
expect(Cypress.backend.getCall(0)).to.be.calledWithMatch(
|
||||
'net',
|
||||
'route:added',
|
||||
{
|
||||
staticResponse: {
|
||||
fixture: {
|
||||
filePath: 'media/cypress.png',
|
||||
encoding: 'base64',
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// @see https://github.com/cypress-io/cypress/issues/8623
|
||||
it('works with images', function () {
|
||||
cy.visit('/fixtures/img-embed.html')
|
||||
|
||||
@@ -1955,6 +1955,7 @@ space
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/14861
|
||||
describe('ignores style and script tag in body', () => {
|
||||
it('style', (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
@@ -1977,6 +1978,35 @@ space
|
||||
cy.visit('fixtures/content-in-body.html')
|
||||
cy.contains('I am in the script tag in body', { timeout: 500 })
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/19377
|
||||
describe('cy.contains() does not remove <style> and <script> tags', () => {
|
||||
it('cy.contains() does not remove style tags from the DOM', () => {
|
||||
cy.visit('fixtures/content-in-body.html')
|
||||
|
||||
cy.get('button#my_button_1').should('be.visible')
|
||||
cy.contains('Hello').should('be.visible')
|
||||
cy.get('button#my_button_1').should('be.visible')
|
||||
})
|
||||
|
||||
it('cy.contains() does not remove script tags from the DOM', () => {
|
||||
cy.visit('fixtures/content-in-body.html')
|
||||
|
||||
cy.window().then((win) => {
|
||||
const scriptElement = win.document.getElementById('my_script')
|
||||
|
||||
expect(scriptElement?.id).to.equal('my_script')
|
||||
})
|
||||
|
||||
cy.get('button#my_button_2').click()
|
||||
cy.contains('This is the result').should('be.visible')
|
||||
cy.window().then((win) => {
|
||||
const scriptElement = win.document.getElementById('my_script')
|
||||
|
||||
expect(scriptElement?.id).to.equal('my_script')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('subject contains text nodes', () => {
|
||||
|
||||
190
packages/driver/cypress/integration/cy/navigation_spec.js
Normal file
190
packages/driver/cypress/integration/cy/navigation_spec.js
Normal file
@@ -0,0 +1,190 @@
|
||||
import { $Location } from '../../../src/cypress/location'
|
||||
import $SetterGetter from '../../../src/cypress/setter_getter'
|
||||
import { bothUrlsMatchAndOneHasHash, historyNavigationTriggeredHashChange } from '../../../src/cy/navigation'
|
||||
|
||||
describe('cy/navigation', () => {
|
||||
describe('.bothUrlsMatchAndOneHasHash', () => {
|
||||
describe('matches remote url', () => {
|
||||
it('when remote url has hash', () => {
|
||||
const current = $Location.create('https://my_url.com')
|
||||
const remote = $Location.create('https://my_url.com#hash')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote)
|
||||
|
||||
expect(isMatch).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('does not match remote url', () => {
|
||||
it('when only current url has hash', () => {
|
||||
const current = $Location.create('https://my_url.com#hash')
|
||||
const remote = $Location.create('https://my_url.com')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
|
||||
it('when remote url is missing hash', () => {
|
||||
const current = $Location.create('https://my_url.com')
|
||||
const remote = $Location.create('https://my_url.com')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
|
||||
it('when origins are different', () => {
|
||||
const current = $Location.create('https://my_url.com')
|
||||
const remote = $Location.create('https://my_other_url.com#hash')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
|
||||
it('when pathnames are different', () => {
|
||||
const current = $Location.create('https://my_url.com/home')
|
||||
const remote = $Location.create('https://my_url.com/random_page#hash')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
|
||||
it('when search is different', () => {
|
||||
const current = $Location.create('https://my_url.com/home?user=1')
|
||||
const remote = $Location.create('https://my_url.com/home#hash?user=1')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('matches least one url', () => {
|
||||
it('when current url has hash', () => {
|
||||
const current = $Location.create('https://my_url.com#title')
|
||||
const remote = $Location.create('https://my_url.com')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote, true)
|
||||
|
||||
expect(isMatch).to.be.true
|
||||
})
|
||||
|
||||
it('when remote url has hash', () => {
|
||||
const current = $Location.create('https://my_url.com')
|
||||
const remote = $Location.create('https://my_url.com#title')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote, true)
|
||||
|
||||
expect(isMatch).to.be.true
|
||||
})
|
||||
|
||||
it('when both urls have a hash', () => {
|
||||
const current = $Location.create('https://my_url.com#home')
|
||||
const remote = $Location.create('https://my_url.com#title')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote, true)
|
||||
|
||||
expect(isMatch).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('does not match either url', () => {
|
||||
it('when neither url has hash', () => {
|
||||
const current = $Location.create('https://my_url.com')
|
||||
const remote = $Location.create('https://my_url.com')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote, true)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
|
||||
it('when origins are different', () => {
|
||||
const current = $Location.create('https://my_url.com')
|
||||
const remote = $Location.create('https://my_other_url.com#hash')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote, true)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
|
||||
it('when pathnames are different', () => {
|
||||
const current = $Location.create('https://my_url.com/home')
|
||||
const remote = $Location.create('https://my_url.com/random_page#hash')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote, true)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
|
||||
it('when search is different', () => {
|
||||
const current = $Location.create('https://my_url.com/home?user=1')
|
||||
const remote = $Location.create('https://my_url.com/home#hash?user=1')
|
||||
const isMatch = bothUrlsMatchAndOneHasHash(current, remote, true)
|
||||
|
||||
expect(isMatch).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.historyNavigationTriggeredHashChange', () => {
|
||||
it('when no navHistoryDelta in state', () => {
|
||||
const state = $SetterGetter.create({ })
|
||||
|
||||
const triggeredHashChange = historyNavigationTriggeredHashChange(state)
|
||||
|
||||
expect(triggeredHashChange).to.be.false
|
||||
})
|
||||
|
||||
it('when navHistoryDelta is 0', () => {
|
||||
const state = $SetterGetter.create({
|
||||
navHistoryDelta: 0,
|
||||
})
|
||||
|
||||
const triggeredHashChange = historyNavigationTriggeredHashChange(state)
|
||||
|
||||
expect(triggeredHashChange).to.be.false
|
||||
})
|
||||
|
||||
it('when navHistoryDelta is null', () => {
|
||||
const state = $SetterGetter.create({
|
||||
navHistoryDelta: null,
|
||||
})
|
||||
|
||||
const triggeredHashChange = historyNavigationTriggeredHashChange(state)
|
||||
|
||||
expect(triggeredHashChange).to.be.false
|
||||
})
|
||||
|
||||
it('when neither url has a hash', () => {
|
||||
const state = $SetterGetter.create({
|
||||
navHistoryDelta: 1,
|
||||
urlPosition: 0,
|
||||
url: 'https://my_url.com/',
|
||||
urls: ['https://my_url.com/', 'https://my_url.com/home'],
|
||||
})
|
||||
|
||||
const triggeredHashChange = historyNavigationTriggeredHashChange(state)
|
||||
|
||||
expect(triggeredHashChange).to.be.false
|
||||
})
|
||||
|
||||
it('when one url has a hash and navigation moves forward in history', () => {
|
||||
const state = $SetterGetter.create({
|
||||
navHistoryDelta: 1,
|
||||
urlPosition: 0,
|
||||
url: 'https://my_url.com/home',
|
||||
urls: ['https://my_url.com/home', 'https://my_url.com/home#hash'],
|
||||
})
|
||||
|
||||
const triggeredHashChange = historyNavigationTriggeredHashChange(state)
|
||||
|
||||
expect(triggeredHashChange).to.be.true
|
||||
expect(state('navHistoryDelta')).to.eq(1)
|
||||
})
|
||||
|
||||
it('when one url has a hash and navigation moves back in history', () => {
|
||||
const state = $SetterGetter.create({
|
||||
navHistoryDelta: -1,
|
||||
urlPosition: 1,
|
||||
url: 'https://my_url.com/home#hash',
|
||||
urls: ['https://my_url.com/home', 'https://my_url.com/home#hash'],
|
||||
})
|
||||
|
||||
const triggeredHashChange = historyNavigationTriggeredHashChange(state)
|
||||
|
||||
expect(triggeredHashChange).to.be.true
|
||||
expect(state('navHistoryDelta')).to.eq(-1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,78 @@
|
||||
const {
|
||||
getBackendStaticResponse,
|
||||
} = require('../../../../src/cy/net-stubbing/static-response-utils')
|
||||
|
||||
describe('driver/src/cy/net-stubbing/static-response-utils', () => {
|
||||
describe('.getBackendStaticResponse', () => {
|
||||
describe('delay', () => {
|
||||
it('does not set delay when delayMS is not provided', () => {
|
||||
const staticResponse = getBackendStaticResponse({})
|
||||
|
||||
expect(staticResponse).to.not.have.property('delay')
|
||||
})
|
||||
|
||||
it('sets delay', () => {
|
||||
const staticResponse = getBackendStaticResponse({ delayMs: 200 })
|
||||
|
||||
expect(staticResponse).to.have.property('delay', 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('fixtures', () => {
|
||||
it('does not set fixtures data when fixtures string is not provided', () => {
|
||||
const staticResponse = getBackendStaticResponse({})
|
||||
|
||||
expect(staticResponse).to.not.have.property('fixtures')
|
||||
})
|
||||
|
||||
it('parses fixture string without file encoding', () => {
|
||||
const staticResponse = getBackendStaticResponse({ fixture: 'file.html' })
|
||||
|
||||
expect(staticResponse.fixture).to.deep.equal({
|
||||
filePath: 'file.html',
|
||||
encoding: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('parses fixture string with file encoding', () => {
|
||||
const staticResponse = getBackendStaticResponse({ fixture: 'file.html,utf8' })
|
||||
|
||||
expect(staticResponse.fixture).to.deep.equal({
|
||||
filePath: 'file.html',
|
||||
encoding: 'utf8',
|
||||
})
|
||||
})
|
||||
|
||||
it('parses fixture string with file encoding set as null to indicate Buffer', () => {
|
||||
const staticResponse = getBackendStaticResponse({ fixture: 'file.html,null' })
|
||||
|
||||
expect(staticResponse.fixture).to.deep.equal({
|
||||
filePath: 'file.html',
|
||||
encoding: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('body', () => {
|
||||
it('does not set body when body is undefined', () => {
|
||||
const staticResponse = getBackendStaticResponse({})
|
||||
|
||||
expect(staticResponse).to.not.have.property('body')
|
||||
})
|
||||
|
||||
it('sets body when body is provided as a string', () => {
|
||||
const staticResponse = getBackendStaticResponse({ body: 'body' })
|
||||
|
||||
expect(staticResponse.body).to.eq('body')
|
||||
})
|
||||
|
||||
it('sets body when body is provided as a ArrayBuffer', () => {
|
||||
const buffer = new ArrayBuffer(8)
|
||||
const staticResponse = getBackendStaticResponse({ body: buffer })
|
||||
|
||||
expect(staticResponse.body).to.be.a('arraybuffer')
|
||||
expect(staticResponse.body).to.eq(buffer)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,62 @@
|
||||
const { Screenshot } = Cypress
|
||||
|
||||
let failedEventFired = false
|
||||
|
||||
Cypress.on('fail', (error) => {
|
||||
failedEventFired = true
|
||||
throw new Error(error)
|
||||
})
|
||||
|
||||
let screenshotTaken = false
|
||||
|
||||
Screenshot.defaults({ onAfterScreenshot: () => {
|
||||
screenshotTaken = true
|
||||
} })
|
||||
|
||||
const pendingTests = []
|
||||
const passedTests = []
|
||||
|
||||
Cypress.on('test:after:run', (test) => {
|
||||
if (test.state === 'pending') {
|
||||
return pendingTests.push(test)
|
||||
}
|
||||
|
||||
if (test.state === 'passed') {
|
||||
return passedTests.push(test)
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Set isInteractive to false to ensure that screenshots will be
|
||||
// triggered in both run and open mode
|
||||
Cypress.config('isInteractive', false)
|
||||
})
|
||||
|
||||
describe('skipped test', () => {
|
||||
it('does not fail', function () {
|
||||
cy.then(() => {
|
||||
this.skip()
|
||||
}).then(() => {
|
||||
expect(true).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
it('does not prevent subsequent tests from running', () => {
|
||||
expect(true).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('skipped test side effects', () => {
|
||||
it('does not have a screenshot taken', () => {
|
||||
expect(screenshotTaken).to.be.false
|
||||
})
|
||||
|
||||
it('does not fire failed event', () => {
|
||||
expect(failedEventFired).to.be.false
|
||||
})
|
||||
|
||||
it('does still mark all tests with the correct state', () => {
|
||||
expect(pendingTests).to.have.length(1)
|
||||
expect(passedTests).to.have.length(3)
|
||||
})
|
||||
})
|
||||
70
packages/driver/cypress/integration/cy/skipping_sync_spec.js
Normal file
70
packages/driver/cypress/integration/cy/skipping_sync_spec.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const { Screenshot } = Cypress
|
||||
|
||||
let failedEventFired = false
|
||||
|
||||
Cypress.on('fail', (error) => {
|
||||
failedEventFired = true
|
||||
throw new Error(error)
|
||||
})
|
||||
|
||||
let screenshotTaken = false
|
||||
|
||||
Screenshot.defaults({ onAfterScreenshot: () => {
|
||||
screenshotTaken = true
|
||||
} })
|
||||
|
||||
const pendingTests = []
|
||||
const passedTests = []
|
||||
|
||||
Cypress.on('test:after:run', (test) => {
|
||||
if (test.state === 'pending') {
|
||||
return pendingTests.push(test)
|
||||
}
|
||||
|
||||
if (test.state === 'passed') {
|
||||
return passedTests.push(test)
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
// Set isInteractive to false to ensure that screenshots will be
|
||||
// triggered in both run and open mode
|
||||
Cypress.config('isInteractive', false)
|
||||
})
|
||||
|
||||
describe('generally skipped test', () => {
|
||||
before(function () {
|
||||
this.skip()
|
||||
})
|
||||
|
||||
it('does not fail', function () {
|
||||
expect(true).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('individually skipped tests', () => {
|
||||
it('does not fail when using this.skip', function () {
|
||||
this.skip()
|
||||
expect(true).to.be.false
|
||||
})
|
||||
|
||||
// NOTE: We are skipping this test in order to test skip functionality
|
||||
it.skip('does not fail when using it.skip', () => {
|
||||
expect(true).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('skipped test side effects', () => {
|
||||
it('does not have a screenshot taken', () => {
|
||||
expect(screenshotTaken).to.be.false
|
||||
})
|
||||
|
||||
it('does not fire failed event', () => {
|
||||
expect(failedEventFired).to.be.false
|
||||
})
|
||||
|
||||
it('does still mark all tests with the correct state', () => {
|
||||
expect(pendingTests).to.have.length(3)
|
||||
expect(passedTests).to.have.length(2)
|
||||
})
|
||||
})
|
||||
@@ -377,6 +377,28 @@ describe('testConfigOverrides baseUrl @slow', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('cannot set read-only properties', () => {
|
||||
afterEach(() => {
|
||||
window.top.__cySkipValidateConfig = true
|
||||
})
|
||||
|
||||
it('throws if mutating read-only config with Cypress.config()', (done) => {
|
||||
window.top.__cySkipValidateConfig = false
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include('`Cypress.config()` cannot mutate option `chromeWebSecurity` because it is a read-only property.')
|
||||
done()
|
||||
})
|
||||
|
||||
Cypress.config('chromeWebSecurity', false)
|
||||
})
|
||||
|
||||
it('does not throw for non-Cypress config values', () => {
|
||||
expect(() => {
|
||||
Cypress.config('foo', 'bar')
|
||||
}).to.not.throw()
|
||||
})
|
||||
})
|
||||
|
||||
function hasOnly (test) {
|
||||
let curSuite = test.parent
|
||||
let hasOnly = false
|
||||
|
||||
16
packages/driver/cypress/integration/issues/17512_spec.js
Normal file
16
packages/driver/cypress/integration/issues/17512_spec.js
Normal file
@@ -0,0 +1,16 @@
|
||||
describe('issue 17512', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('fixtures/issue-17512.html')
|
||||
})
|
||||
|
||||
it('returns null when target is not defined', () => {
|
||||
cy.get('#link').click()
|
||||
cy.get('#result').should('have.text', 'null')
|
||||
|
||||
cy.get('#link2').click()
|
||||
cy.get('#result2').should('have.text', '"_top"')
|
||||
|
||||
cy.get('#link3').click()
|
||||
cy.get('#result3').should('have.text', 'null')
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,8 @@ const { $ } = Cypress
|
||||
|
||||
const isActuallyInteractive = Cypress.config('isInteractive')
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
|
||||
if (!isActuallyInteractive) {
|
||||
// we want to only enable retries in runMode
|
||||
// and because we set `isInteractive` above
|
||||
|
||||
@@ -64,6 +64,17 @@ index 57a2761..0000000
|
||||
@@ -1 +0,0 @@
|
||||
-{"version":3,"file":"unfetch.umd.js","sources":["../src/index.mjs"],"sourcesContent":["export default function(url, options) {\n\toptions = options || {};\n\treturn new Promise( (resolve, reject) => {\n\t\tconst request = new XMLHttpRequest();\n\t\tconst keys = [];\n\t\tconst all = [];\n\t\tconst headers = {};\n\n\t\tconst response = () => ({\n\t\t\tok: (request.status/100|0) == 2,\t\t// 200-299\n\t\t\tstatusText: request.statusText,\n\t\t\tstatus: request.status,\n\t\t\turl: request.responseURL,\n\t\t\ttext: () => Promise.resolve(request.responseText),\n\t\t\tjson: () => Promise.resolve(JSON.parse(request.responseText)),\n\t\t\tblob: () => Promise.resolve(new Blob([request.response])),\n\t\t\tclone: response,\n\t\t\theaders: {\n\t\t\t\tkeys: () => keys,\n\t\t\t\tentries: () => all,\n\t\t\t\tget: n => headers[n.toLowerCase()],\n\t\t\t\thas: n => n.toLowerCase() in headers\n\t\t\t}\n\t\t});\n\n\t\trequest.open(options.method || 'get', url, true);\n\n\t\trequest.onload = () => {\n\t\t\trequest.getAllResponseHeaders().replace(/^(.*?):[^\\S\\n]*([\\s\\S]*?)$/gm, (m, key, value) => {\n\t\t\t\tkeys.push(key = key.toLowerCase());\n\t\t\t\tall.push([key, value]);\n\t\t\t\theaders[key] = headers[key] ? `${headers[key]},${value}` : value;\n\t\t\t});\n\t\t\tresolve(response());\n\t\t};\n\n\t\trequest.onerror = reject;\n\n\t\trequest.withCredentials = options.credentials=='include';\n\n\t\tfor (const i in options.headers) {\n\t\t\trequest.setRequestHeader(i, options.headers[i]);\n\t\t}\n\n\t\trequest.send(options.body || null);\n\t});\n}\n"],"names":["url","options","Promise","resolve","reject","request","XMLHttpRequest","keys","all","headers","response","ok","status","statusText","responseURL","text","responseText","json","JSON","parse","blob","Blob","clone","entries","get","n","toLowerCase","has","const","i","open","method","onload","getAllResponseHeaders","replace","m","key","value","push","onerror","withCredentials","credentials","setRequestHeader","send","body"],"mappings":"6KAAe,SAASA,EAAKC,UAC5BA,EAAUA,GAAW,GACd,IAAIC,iBAAUC,EAASC,OACvBC,EAAU,IAAIC,eACdC,EAAO,GACPC,EAAM,GACNC,EAAU,GAEVC,oBACLC,GAA8B,IAAzBN,EAAQO,OAAO,IAAI,GACxBC,WAAYR,EAAQQ,WACpBD,OAAQP,EAAQO,OAChBZ,IAAKK,EAAQS,YACbC,uBAAYb,QAAQC,QAAQE,EAAQW,eACpCC,uBAAYf,QAAQC,QAAQe,KAAKC,MAAMd,EAAQW,gBAC/CI,uBAAYlB,QAAQC,QAAQ,IAAIkB,KAAK,CAAChB,EAAQK,aAC9CY,MAAOZ,EACPD,QAAS,CACRF,uBAAYA,GACZgB,0BAAef,GACfgB,aAAKC,UAAKhB,EAAQgB,EAAEC,gBACpBC,aAAKF,UAAKA,EAAEC,gBAAiBjB,UAmB1BmB,IAAMC,KAfXxB,EAAQyB,KAAK7B,EAAQ8B,QAAU,MAAO/B,GAAK,GAE3CK,EAAQ2B,kBACP3B,EAAQ4B,wBAAwBC,QAAQ,wCAAiCC,EAAGC,EAAKC,GAChF9B,EAAK+B,KAAKF,EAAMA,EAAIV,eACpBlB,EAAI8B,KAAK,CAACF,EAAKC,IACf5B,EAAQ2B,GAAO3B,EAAQ2B,GAAU3B,EAAQ2B,OAAQC,EAAUA,IAE5DlC,EAAQO,MAGTL,EAAQkC,QAAUnC,EAElBC,EAAQmC,gBAAuC,WAArBvC,EAAQwC,YAElBxC,EAAQQ,QACvBJ,EAAQqC,iBAAiBb,EAAG5B,EAAQQ,QAAQoB,IAG7CxB,EAAQsC,KAAK1C,EAAQ2C,MAAQ"}
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/unfetch/src/index.d.ts b/node_modules/unfetch/src/index.d.ts
|
||||
index 0c53ad9..8dd2d54 100644
|
||||
--- a/node_modules/unfetch/src/index.d.ts
|
||||
+++ b/node_modules/unfetch/src/index.d.ts
|
||||
@@ -14,4 +14,6 @@ declare namespace unfetch {
|
||||
|
||||
declare const unfetch: typeof fetch;
|
||||
|
||||
+export function registerFetch(window: Window): unfetch;
|
||||
+
|
||||
export default unfetch;
|
||||
diff --git a/node_modules/unfetch/src/index.mjs b/node_modules/unfetch/src/index.mjs
|
||||
deleted file mode 100644
|
||||
index 783ad42..0000000
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import _ from 'lodash'
|
||||
import Promise from 'bluebird'
|
||||
|
||||
@@ -54,12 +52,18 @@ const isDomSubjectAndMatchesValue = (value, subject) => {
|
||||
return false
|
||||
}
|
||||
|
||||
type Parsed = {
|
||||
subject?: JQuery<any>
|
||||
actual?: any
|
||||
expected?: any
|
||||
}
|
||||
|
||||
// Rules:
|
||||
// 1. always remove value
|
||||
// 2. if value is a jquery object set a subject
|
||||
// 3. if actual is undefined or its not expected remove both actual + expected
|
||||
const parseValueActualAndExpected = (value, actual, expected) => {
|
||||
const obj = { actual, expected }
|
||||
const obj: Parsed = { actual, expected }
|
||||
|
||||
if ($dom.isJquery(value)) {
|
||||
obj.subject = value
|
||||
@@ -77,7 +81,7 @@ export const create = (Cypress, cy) => {
|
||||
const getUpcomingAssertions = () => {
|
||||
const index = cy.state('index') + 1
|
||||
|
||||
const assertions = []
|
||||
const assertions: any[] = []
|
||||
|
||||
// grab the rest of the queue'd commands
|
||||
for (let cmd of cy.queue.slice(index)) {
|
||||
@@ -137,15 +141,29 @@ export const create = (Cypress, cy) => {
|
||||
message = message.replace(stackTracesRe, '\n')
|
||||
}
|
||||
|
||||
let obj = parseValueActualAndExpected(value, actual, expected)
|
||||
let parsed = parseValueActualAndExpected(value, actual, expected)
|
||||
// TODO: make it more specific after defining the type for Cypress.log().
|
||||
let obj: Record<string, any> = {
|
||||
...parsed,
|
||||
}
|
||||
|
||||
if ($dom.isElement(value)) {
|
||||
obj.$el = $dom.wrap(value)
|
||||
}
|
||||
|
||||
// if we are simply verifying the upcoming
|
||||
// assertions then do not immediately end or snapshot
|
||||
// else do
|
||||
// `verifying` represents whether we're deciding whether or not to resolve
|
||||
// a command (true) or of we're actually performing a user-facing assertion
|
||||
// (false).
|
||||
|
||||
// If we're verifying upcoming assertions (implicit or explicit),
|
||||
// then we don't need to take a DOM snapshot - one will be taken later when
|
||||
// retries time out or the command otherwise entirely fails or passes.
|
||||
// We save the error on _error because we may use it to construct the
|
||||
// timeout error which we eventually do display to the user.
|
||||
|
||||
// If we're actually performing an assertion which will be displayed to the
|
||||
// user though, then we want to take a DOM snapshot and display this error
|
||||
// (if any) in the log message on screen.
|
||||
if (verifying) {
|
||||
obj._error = error
|
||||
} else {
|
||||
@@ -216,10 +234,18 @@ export const create = (Cypress, cy) => {
|
||||
})
|
||||
}
|
||||
|
||||
type VerifyUpcomingAssertionsCallbacks = {
|
||||
ensureExistenceFor?: 'subject' | 'dom' | boolean
|
||||
onPass?: Function
|
||||
onFail?: (err?, isDefaultAssertionErr?: boolean, cmds?: any[]) => void
|
||||
onRetry?: () => any
|
||||
}
|
||||
|
||||
return {
|
||||
finishAssertions,
|
||||
|
||||
verifyUpcomingAssertions (subject, options = {}, callbacks = {}) {
|
||||
// TODO: define the specific type of options
|
||||
verifyUpcomingAssertions (subject, options: Record<string, any> = {}, callbacks: VerifyUpcomingAssertionsCallbacks = {}) {
|
||||
const cmds = getUpcomingAssertions()
|
||||
|
||||
cy.state('upcomingAssertions', cmds)
|
||||
@@ -267,6 +293,7 @@ export const create = (Cypress, cy) => {
|
||||
}
|
||||
|
||||
const onPassFn = () => {
|
||||
cy.state('overrideAssert', undefined)
|
||||
if (_.isFunction(callbacks.onPass)) {
|
||||
return callbacks.onPass.call(this, cmds, options.assertions)
|
||||
}
|
||||
@@ -289,6 +316,7 @@ export const create = (Cypress, cy) => {
|
||||
err = e2
|
||||
}
|
||||
|
||||
cy.state('overrideAssert', undefined)
|
||||
err.isDefaultAssertionErr = isDefaultAssertionErr
|
||||
|
||||
options.error = err
|
||||
@@ -324,6 +352,24 @@ export const create = (Cypress, cy) => {
|
||||
// bail if we have no assertions and apply
|
||||
// the default assertions if applicable
|
||||
if (!cmds.length) {
|
||||
// In general in cypress, when assertions fail we want to take a DOM
|
||||
// snapshot to display to the user. In this case though, when we invoke
|
||||
// ensureExistence, we're not going to display the error (if there is
|
||||
// one) to the user - we're only deciding whether to resolve this current
|
||||
// command (assertions pass) or fail (and probably retry). A DOM snapshot
|
||||
// isn't necessary in either case - one will be taken later as part of the
|
||||
// command (if they pass) or when we time out retrying.
|
||||
|
||||
// Chai assertions have a signature of (passed, message, value, actual,
|
||||
// expected, error). Our assertFn, defined earlier in the file, adds
|
||||
// on a 7th arg, "verifying", which defaults to false. We here override
|
||||
// the assert function with our own, which just invokes the old one
|
||||
// with verifying = true. This override is cleaned up immediately
|
||||
// afterwards, in either onPassFn or onFailFn.
|
||||
cy.state('overrideAssert', function (...args) {
|
||||
return assertFn.apply(this, args.concat(true) as any)
|
||||
})
|
||||
|
||||
return Promise
|
||||
.try(ensureExistence)
|
||||
.then(onPassFn)
|
||||
@@ -433,12 +479,13 @@ export const create = (Cypress, cy) => {
|
||||
cy.state('onBeforeLog', setCommandLog)
|
||||
|
||||
// send verify=true as the last arg
|
||||
return assertFn.apply(this, args.concat(true))
|
||||
return assertFn.apply(this, args.concat(true) as any)
|
||||
}
|
||||
|
||||
const fns = injectAssertionFns(cmds)
|
||||
|
||||
const subjects = []
|
||||
// TODO: remove any when the type of subject, the first argument of this function is specified.
|
||||
const subjects: any[] = []
|
||||
|
||||
// iterate through each subject
|
||||
// and force the assertion to return
|
||||
@@ -477,8 +524,6 @@ export const create = (Cypress, cy) => {
|
||||
return cy.state('overrideAssert', undefined)
|
||||
}
|
||||
|
||||
// store this in case our test ends early
|
||||
// and we reset between tests
|
||||
cy.state('overrideAssert', overrideAssert)
|
||||
|
||||
return Promise
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
/* eslint-disable prefer-rest-params */
|
||||
// tests in driver/cypress/integration/commands/assertions_spec.js
|
||||
|
||||
@@ -31,20 +30,6 @@ const whitespace = /\s/g
|
||||
const valueHasLeadingOrTrailingWhitespaces = /\*\*'\s+|\s+'\*\*/g
|
||||
const imageMarkdown = /!\[.*?\]\(.*?\)/g
|
||||
|
||||
let assertProto = null
|
||||
let matchProto = null
|
||||
let lengthProto = null
|
||||
let containProto = null
|
||||
let existProto = null
|
||||
let getMessage = null
|
||||
let chaiUtils = null
|
||||
let replaceArgMessages = null
|
||||
let removeOrKeepSingleQuotesBetweenStars = null
|
||||
let setSpecWindowGlobals = null
|
||||
let restoreAsserts = null
|
||||
let overrideExpect = null
|
||||
let overrideChaiAsserts = null
|
||||
|
||||
type CreateFunc = ((specWindow, state, assertFn) => ({
|
||||
chai: Chai.ChaiStatic
|
||||
expect: (val: any, message?: string) => Chai.Assertion
|
||||
@@ -55,7 +40,7 @@ export let create: CreateFunc | null = null
|
||||
chai.use(sinonChai)
|
||||
|
||||
chai.use((chai, u) => {
|
||||
chaiUtils = u
|
||||
const chaiUtils = u
|
||||
|
||||
$chaiJquery(chai, chaiUtils, {
|
||||
onInvalid (method, obj) {
|
||||
@@ -87,14 +72,14 @@ chai.use((chai, u) => {
|
||||
},
|
||||
})
|
||||
|
||||
assertProto = chai.Assertion.prototype.assert
|
||||
matchProto = chai.Assertion.prototype.match
|
||||
lengthProto = chai.Assertion.prototype.__methods.length.method
|
||||
containProto = chai.Assertion.prototype.__methods.contain.method
|
||||
existProto = Object.getOwnPropertyDescriptor(chai.Assertion.prototype, 'exist').get
|
||||
const { objDisplay } = chai.util;
|
||||
const assertProto = chai.Assertion.prototype.assert
|
||||
const matchProto = (chai.Assertion.prototype as any).match
|
||||
const lengthProto = (chai.Assertion.prototype as any).__methods.length.method
|
||||
const containProto = (chai.Assertion.prototype as any).__methods.contain.method
|
||||
const existProto = Object.getOwnPropertyDescriptor(chai.Assertion.prototype, 'exist')!.get
|
||||
const { objDisplay } = chai.util
|
||||
|
||||
({ getMessage } = chai.util)
|
||||
const getMessage = chai.util.getMessage
|
||||
const _inspect = chai.util.inspect
|
||||
|
||||
const { inspect, setFormatValueHook } = chaiInspect.create(chai)
|
||||
@@ -108,17 +93,19 @@ chai.use((chai, u) => {
|
||||
try {
|
||||
val && val.document
|
||||
val && val.inspect
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
if (e.stack.indexOf('cross-origin') !== -1 || // chrome
|
||||
e.message.indexOf('cross-origin') !== -1) { // firefox
|
||||
return `[window]`
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
// remove any single quotes between our **,
|
||||
// except escaped quotes, empty strings and number strings.
|
||||
removeOrKeepSingleQuotesBetweenStars = (message) => {
|
||||
const removeOrKeepSingleQuotesBetweenStars = (message) => {
|
||||
// remove any single quotes between our **, preserving escaped quotes
|
||||
// and if an empty string, put the quotes back
|
||||
return message.replace(allBetweenFourStars, (match) => {
|
||||
@@ -149,8 +136,8 @@ chai.use((chai, u) => {
|
||||
return message.replace(imageMarkdown, '``$&``')
|
||||
}
|
||||
|
||||
replaceArgMessages = (args, str) => {
|
||||
return _.reduce(args, (memo, value, index) => {
|
||||
const replaceArgMessages = (args, str) => {
|
||||
return _.reduce(args, (memo: string[], value, index) => {
|
||||
if (_.isString(value)) {
|
||||
value = value
|
||||
.replace(allWordsBetweenCurlyBraces, '**$1**')
|
||||
@@ -166,18 +153,17 @@ chai.use((chai, u) => {
|
||||
}
|
||||
|
||||
return memo
|
||||
}
|
||||
, [])
|
||||
}, [])
|
||||
}
|
||||
|
||||
restoreAsserts = function () {
|
||||
const restoreAsserts = function () {
|
||||
chai.util.inspect = _inspect
|
||||
chai.util.getMessage = getMessage
|
||||
chai.util.objDisplay = objDisplay
|
||||
chai.Assertion.prototype.assert = assertProto
|
||||
chai.Assertion.prototype.match = matchProto
|
||||
chai.Assertion.prototype.__methods.length.method = lengthProto
|
||||
chai.Assertion.prototype.__methods.contain.method = containProto
|
||||
chai.Assertion.prototype.assert = assertProto;
|
||||
(chai.Assertion.prototype as any).match = matchProto;
|
||||
(chai.Assertion.prototype as any).__methods.length.method = lengthProto;
|
||||
(chai.Assertion.prototype as any).__methods.contain.method = containProto
|
||||
|
||||
return Object.defineProperty(chai.Assertion.prototype, 'exist', { get: existProto })
|
||||
}
|
||||
@@ -218,7 +204,7 @@ chai.use((chai, u) => {
|
||||
}
|
||||
}
|
||||
|
||||
overrideChaiAsserts = function (specWindow, state, assertFn) {
|
||||
const overrideChaiAsserts = function (specWindow, state, assertFn) {
|
||||
chai.Assertion.prototype.assert = createPatchedAssert(specWindow, state, assertFn)
|
||||
|
||||
const _origGetmessage = function (obj, args) {
|
||||
@@ -248,6 +234,9 @@ chai.use((chai, u) => {
|
||||
return (flagMsg ? `${flagMsg}: ${msg}` : msg)
|
||||
}
|
||||
|
||||
// There are 2 types of getMessage. And we're overriding the second one.
|
||||
// But TypeScript wants us to do both. So we're ignoring this.
|
||||
// @ts-ignore
|
||||
chaiUtils.getMessage = function (assert, args) {
|
||||
const obj = assert._obj
|
||||
|
||||
@@ -307,13 +296,16 @@ chai.use((chai, u) => {
|
||||
})
|
||||
}
|
||||
|
||||
const containFn2 = (_super) => {
|
||||
const makeMethodChainable = (_super) => {
|
||||
return (function () {
|
||||
return _super.apply(this, arguments)
|
||||
})
|
||||
}
|
||||
|
||||
chai.Assertion.overwriteChainableMethod('contain', containFn1, containFn2)
|
||||
// `makeMethodChainable` doesn't match any type definition,
|
||||
// but it is necessary to make the method chainable.
|
||||
// @ts-ignore
|
||||
chai.Assertion.overwriteChainableMethod('contain', containFn1, makeMethodChainable)
|
||||
|
||||
chai.Assertion.overwriteChainableMethod('length',
|
||||
(_super) => {
|
||||
@@ -346,7 +338,7 @@ chai.use((chai, u) => {
|
||||
length,
|
||||
obj.length,
|
||||
)
|
||||
} catch (e1) {
|
||||
} catch (e1: any) {
|
||||
e1.node = node
|
||||
e1.negated = chaiUtils.flag(this, 'negate')
|
||||
e1.type = 'length'
|
||||
@@ -377,21 +369,21 @@ chai.use((chai, u) => {
|
||||
}
|
||||
})
|
||||
},
|
||||
// `makeMethodChainable` doesn't match any type definition,
|
||||
// but it is necessary to make the method chainable.
|
||||
// @ts-ignore
|
||||
makeMethodChainable)
|
||||
|
||||
(_super) => {
|
||||
return (function () {
|
||||
return _super.apply(this, arguments)
|
||||
})
|
||||
})
|
||||
|
||||
return chai.Assertion.overwriteProperty('exist', (_super) => {
|
||||
// _super is not documented.
|
||||
// @ts-ignore
|
||||
chai.Assertion.overwriteProperty('exist', (_super) => {
|
||||
return (function () {
|
||||
const obj = this._obj
|
||||
|
||||
if (!($dom.isJquery(obj) || $dom.isElement(obj))) {
|
||||
try {
|
||||
return _super.apply(this, arguments)
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
e.type = 'existence'
|
||||
throw e
|
||||
}
|
||||
@@ -412,7 +404,7 @@ chai.use((chai, u) => {
|
||||
node,
|
||||
node,
|
||||
)
|
||||
} catch (e1) {
|
||||
} catch (e1: any) {
|
||||
e1.node = node
|
||||
e1.negated = chaiUtils.flag(this, 'negate')
|
||||
e1.type = 'existence'
|
||||
@@ -456,20 +448,20 @@ chai.use((chai, u) => {
|
||||
const createPatchedAssert = (specWindow, state, assertFn) => {
|
||||
return (function (...args) {
|
||||
let err
|
||||
const passed = chaiUtils.test(this, args)
|
||||
const passed = chaiUtils.test(this, args as Chai.AssertionArgs)
|
||||
const value = chaiUtils.flag(this, 'object')
|
||||
const expected = args[3]
|
||||
|
||||
const customArgs = replaceArgMessages(args, this._obj)
|
||||
|
||||
let message = chaiUtils.getMessage(this, customArgs)
|
||||
const actual = chaiUtils.getActual(this, customArgs)
|
||||
let message = chaiUtils.getMessage(this, customArgs as Chai.AssertionArgs)
|
||||
const actual = chaiUtils.getActual(this, customArgs as Chai.AssertionArgs)
|
||||
|
||||
message = removeOrKeepSingleQuotesBetweenStars(message)
|
||||
message = escapeMarkdown(message)
|
||||
|
||||
try {
|
||||
assertProto.apply(this, args)
|
||||
assertProto.apply(this, args as Chai.AssertionArgs)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
@@ -489,7 +481,7 @@ chai.use((chai, u) => {
|
||||
})
|
||||
}
|
||||
|
||||
overrideExpect = (specWindow, state) => {
|
||||
const overrideExpect = (specWindow, state) => {
|
||||
// only override assertions for this specific
|
||||
// expect function instance so we do not affect
|
||||
// the outside world
|
||||
@@ -523,7 +515,7 @@ chai.use((chai, u) => {
|
||||
return fn
|
||||
}
|
||||
|
||||
setSpecWindowGlobals = function (specWindow, state) {
|
||||
const setSpecWindowGlobals = function (specWindow, state) {
|
||||
const expect = overrideExpect(specWindow, state)
|
||||
const assert = overrideAssert(specWindow, state)
|
||||
|
||||
@@ -552,12 +544,3 @@ chai.use((chai, u) => {
|
||||
export interface IChai {
|
||||
expect: ReturnType<CreateFunc>['expect']
|
||||
}
|
||||
|
||||
export default {
|
||||
replaceArgMessages,
|
||||
removeOrKeepSingleQuotesBetweenStars,
|
||||
setSpecWindowGlobals,
|
||||
restoreAsserts,
|
||||
overrideExpect,
|
||||
overrideChaiAsserts,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import Promise from 'bluebird'
|
||||
import $utils from '../../cypress/utils'
|
||||
import $errUtils from '../../cypress/error_utils'
|
||||
import $Log from '../../cypress/log'
|
||||
import { bothUrlsMatchAndOneHasHash } from '../navigation'
|
||||
import { $Location } from '../../cypress/location'
|
||||
|
||||
import debugFn from 'debug'
|
||||
@@ -60,19 +61,6 @@ const timedOutWaitingForPageLoad = (ms, log) => {
|
||||
})
|
||||
}
|
||||
|
||||
const bothUrlsMatchAndRemoteHasHash = (current, remote) => {
|
||||
// the remote has a hash
|
||||
// or the last char of href
|
||||
// is a hash
|
||||
return (remote.hash || remote.href.slice(-1) === '#') &&
|
||||
// both must have the same origin
|
||||
current.origin === remote.origin &&
|
||||
// both must have the same pathname
|
||||
current.pathname === remote.pathname &&
|
||||
// both must have the same query params
|
||||
current.search === remote.search
|
||||
}
|
||||
|
||||
const cannotVisitDifferentOrigin = (origin, previousUrlVisited, remoteUrl, existingUrl, log) => {
|
||||
const differences = []
|
||||
|
||||
@@ -129,13 +117,18 @@ const navigationChanged = (Cypress, cy, state, source, arg) => {
|
||||
}
|
||||
|
||||
// start storing the history entries
|
||||
const urls = state('urls') || []
|
||||
let urls = state('urls') || []
|
||||
let urlPosition = state('urlPosition')
|
||||
|
||||
const previousUrl = _.last(urls)
|
||||
if (urlPosition === undefined) {
|
||||
urlPosition = -1
|
||||
}
|
||||
|
||||
// ensure our new url doesnt match whatever
|
||||
const previousUrl = urls[urlPosition]
|
||||
|
||||
// ensure our new url doesn't match whatever
|
||||
// the previous was. this prevents logging
|
||||
// additionally when the url didnt actually change
|
||||
// additionally when the url didn't actually change
|
||||
if (url === previousUrl) {
|
||||
return
|
||||
}
|
||||
@@ -143,11 +136,23 @@ const navigationChanged = (Cypress, cy, state, source, arg) => {
|
||||
// else notify the world and log this event
|
||||
Cypress.action('cy:url:changed', url)
|
||||
|
||||
urls.push(url)
|
||||
const navHistoryDelta = state('navHistoryDelta')
|
||||
|
||||
// if navigation was changed via a manipulation of the browser session we
|
||||
// need to update the urlPosition to match the position of the history stack
|
||||
// and we do not need to push a new url onto the urls state
|
||||
if (navHistoryDelta) {
|
||||
urlPosition = urlPosition + navHistoryDelta
|
||||
state('navHistoryDelta', undefined)
|
||||
} else {
|
||||
urls = urls.slice(0, urlPosition + 1)
|
||||
urls.push(url)
|
||||
urlPosition = urlPosition + 1
|
||||
}
|
||||
|
||||
state('urls', urls)
|
||||
|
||||
state('url', url)
|
||||
state('urlPosition', urlPosition)
|
||||
|
||||
// don't output a command log for 'load' or 'before:load' events
|
||||
// return if source in command
|
||||
@@ -586,10 +591,8 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
})
|
||||
},
|
||||
|
||||
go (numberOrString, options = {}) {
|
||||
const userOptions = options
|
||||
|
||||
options = _.defaults({}, userOptions, {
|
||||
go (numberOrString, userOptions = {}) {
|
||||
const options = _.defaults({}, userOptions, {
|
||||
log: true,
|
||||
timeout: config('pageLoadTimeout'),
|
||||
})
|
||||
@@ -931,7 +934,7 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
// the browser won't actually make a new http request
|
||||
// for this, and so we need to resolve onLoad immediately
|
||||
// and bypass the actual visit resolution stuff
|
||||
if (bothUrlsMatchAndRemoteHasHash(current, remote)) {
|
||||
if (bothUrlsMatchAndOneHasHash(current, remote)) {
|
||||
// https://github.com/cypress-io/cypress/issues/1311
|
||||
if (current.hash === remote.hash) {
|
||||
consoleProps['Note'] = 'Because this visit was to the same hash, the page did not reload and the onBeforeLoad and onLoad callbacks did not fire.'
|
||||
|
||||
@@ -114,6 +114,13 @@ export type KeyEventType =
|
||||
| 'textInput'
|
||||
| 'beforeinput'
|
||||
|
||||
export type ModifiersEventOptions = {
|
||||
altKey: boolean
|
||||
ctrlKey: boolean
|
||||
metaKey: boolean
|
||||
shiftKey: boolean
|
||||
}
|
||||
|
||||
const toModifiersEventOptions = (modifiers: KeyboardModifiers) => {
|
||||
return {
|
||||
altKey: modifiers.alt,
|
||||
|
||||
@@ -4,6 +4,7 @@ import _ from 'lodash'
|
||||
import { handleInvalidEventTarget, handleInvalidAnchorTarget } from './top_attr_guards'
|
||||
|
||||
const HISTORY_ATTRS = 'pushState replaceState'.split(' ')
|
||||
const HISTORY_NAV_ATTRS = 'go back forward'.split(' ')
|
||||
|
||||
let events = []
|
||||
let listenersAdded = null
|
||||
@@ -81,6 +82,20 @@ export default {
|
||||
callbacks.onNavigation('hashchange', e)
|
||||
})
|
||||
|
||||
for (let attr of HISTORY_NAV_ATTRS) {
|
||||
const orig = contentWindow.history?.[attr]
|
||||
|
||||
if (!orig) {
|
||||
continue
|
||||
}
|
||||
|
||||
contentWindow.history[attr] = function (delta) {
|
||||
callbacks.onHistoryNav(attr === 'back' ? -1 : (attr === 'forward' ? 1 : delta))
|
||||
|
||||
orig.apply(this, [delta])
|
||||
}
|
||||
}
|
||||
|
||||
for (let attr of HISTORY_ATTRS) {
|
||||
const orig = contentWindow.history?.[attr]
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import $ from 'jquery'
|
||||
import _ from 'lodash'
|
||||
import $dom from '../dom'
|
||||
import $elements from '../dom/elements'
|
||||
import $Keyboard from './keyboard'
|
||||
import $Keyboard, { ModifiersEventOptions } from './keyboard'
|
||||
import $selection from '../dom/selection'
|
||||
import debugFn from 'debug'
|
||||
|
||||
@@ -16,7 +15,7 @@ const debug = debugFn('cypress:driver:mouse')
|
||||
* @property {Document} doc
|
||||
*/
|
||||
|
||||
const getLastHoveredEl = (state) => {
|
||||
const getLastHoveredEl = (state): HTMLElement | null => {
|
||||
let lastHoveredEl = state('mouseLastHoveredEl')
|
||||
const lastHoveredElAttached = lastHoveredEl && $elements.isAttachedEl(lastHoveredEl)
|
||||
|
||||
@@ -42,6 +41,12 @@ const getMouseCoords = (state) => {
|
||||
return state('mouseCoords')
|
||||
}
|
||||
|
||||
type DefaultMouseOptions = ModifiersEventOptions & CoordsEventOptions & {
|
||||
view: Window
|
||||
composed: boolean
|
||||
relatedTarget: HTMLElement | null
|
||||
}
|
||||
|
||||
export const create = (state, keyboard, focused, Cypress) => {
|
||||
const isFirefox = Cypress.browser.family === 'firefox'
|
||||
|
||||
@@ -65,7 +70,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
|
||||
return sendPointerEvent(el, evtOptions, 'pointerup', true, true)
|
||||
}
|
||||
const sendPointerdown = (el, evtOptions) => {
|
||||
const sendPointerdown = (el, evtOptions): {} | SentEvent => {
|
||||
if (isFirefox && el.disabled) {
|
||||
return {}
|
||||
}
|
||||
@@ -75,7 +80,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
const sendPointermove = (el, evtOptions) => {
|
||||
return sendPointerEvent(el, evtOptions, 'pointermove', true, true)
|
||||
}
|
||||
const sendPointerover = (el, evtOptions) => {
|
||||
const sendPointerover = (el, evtOptions: DefaultMouseOptions) => {
|
||||
return sendPointerEvent(el, evtOptions, 'pointerover', true, true)
|
||||
}
|
||||
const sendPointerenter = (el, evtOptions) => {
|
||||
@@ -95,7 +100,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
|
||||
return sendMouseEvent(el, evtOptions, 'mouseup', true, true)
|
||||
}
|
||||
const sendMousedown = (el, evtOptions) => {
|
||||
const sendMousedown = (el, evtOptions): {} | SentEvent => {
|
||||
if (isFirefox && el.disabled) {
|
||||
return {}
|
||||
}
|
||||
@@ -105,7 +110,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
const sendMousemove = (el, evtOptions) => {
|
||||
return sendMouseEvent(el, evtOptions, 'mousemove', true, true)
|
||||
}
|
||||
const sendMouseover = (el, evtOptions) => {
|
||||
const sendMouseover = (el, evtOptions: DefaultMouseOptions) => {
|
||||
return sendMouseEvent(el, evtOptions, 'mouseover', true, true)
|
||||
}
|
||||
const sendMouseenter = (el, evtOptions) => {
|
||||
@@ -117,7 +122,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
const sendMouseout = (el, evtOptions) => {
|
||||
return sendMouseEvent(el, evtOptions, 'mouseout', true, true)
|
||||
}
|
||||
const sendClick = (el, evtOptions, opts = {}) => {
|
||||
const sendClick = (el, evtOptions, opts: { force?: boolean } = {}) => {
|
||||
// send the click event if firefox and force (needed for force check checkbox)
|
||||
if (!opts.force && isFirefox && el.disabled) {
|
||||
return {}
|
||||
@@ -154,7 +159,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
return !_.isEqual(xy(fromElViewport), xy(coords))
|
||||
}
|
||||
|
||||
const shouldMoveCursorToEndAfterMousedown = (el) => {
|
||||
const shouldMoveCursorToEndAfterMousedown = (el: HTMLElement) => {
|
||||
const _debug = debug.extend(':shouldMoveCursorToEnd')
|
||||
|
||||
_debug('shouldMoveToEnd?', el)
|
||||
@@ -182,7 +187,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
}
|
||||
|
||||
const mouse = {
|
||||
_getDefaultMouseOptions (x, y, win) {
|
||||
_getDefaultMouseOptions (x, y, win): DefaultMouseOptions {
|
||||
const _activeModifiers = keyboard.getActiveModifiers()
|
||||
const modifiersEventOptions = $Keyboard.toModifiersEventOptions(_activeModifiers)
|
||||
const coordsEventOptions = toCoordsEventOptions(x, y, win)
|
||||
@@ -201,7 +206,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
* @param {Coords} coords
|
||||
* @param {HTMLElement} forceEl
|
||||
*/
|
||||
move (fromElViewport, forceEl) {
|
||||
move (fromElViewport, forceEl?) {
|
||||
debug('mouse.move', fromElViewport)
|
||||
|
||||
const lastHoveredEl = getLastHoveredEl(state)
|
||||
@@ -259,16 +264,21 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
skipped: formatReasonNotFired('Already on Coordinates'),
|
||||
}
|
||||
}
|
||||
|
||||
type EventFunc =
|
||||
| (() => { skipped: string })
|
||||
| (() => SentEvent)
|
||||
|
||||
let pointerout = _.noop
|
||||
let pointerleave = _.noop
|
||||
let pointerover = notFired
|
||||
let pointerover: EventFunc = notFired
|
||||
let pointerenter = _.noop
|
||||
let mouseout = _.noop
|
||||
let mouseleave = _.noop
|
||||
let mouseover = notFired
|
||||
let mouseover: EventFunc = notFired
|
||||
let mouseenter = _.noop
|
||||
let pointermove = notFired
|
||||
let mousemove = notFired
|
||||
let pointermove: EventFunc = notFired
|
||||
let mousemove: EventFunc = notFired
|
||||
|
||||
const lastHoveredEl = getLastHoveredEl(state)
|
||||
|
||||
@@ -285,9 +295,9 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
sendMouseout(lastHoveredEl, _.extend({}, defaultMouseOptions, { relatedTarget: el }))
|
||||
}
|
||||
|
||||
let curParent = lastHoveredEl
|
||||
let curParent: Node | null = lastHoveredEl
|
||||
|
||||
const elsToSendMouseleave = []
|
||||
const elsToSendMouseleave: Node[] = []
|
||||
|
||||
while (curParent && curParent.ownerDocument && curParent !== commonAncestor) {
|
||||
elsToSendMouseleave.push(curParent)
|
||||
@@ -317,8 +327,8 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
return sendPointerover(el, _.extend({}, defaultPointerOptions, { relatedTarget: lastHoveredEl }))
|
||||
}
|
||||
|
||||
let curParent = el
|
||||
const elsToSendMouseenter = []
|
||||
let curParent: Node | null = el
|
||||
const elsToSendMouseenter: Node[] = []
|
||||
|
||||
while (curParent && curParent.ownerDocument && curParent !== commonAncestor) {
|
||||
elsToSendMouseenter.push(curParent)
|
||||
@@ -349,7 +359,8 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
return sendMousemove(el, defaultMouseOptions)
|
||||
}
|
||||
|
||||
const events = []
|
||||
// TODO: make `type` below more specific.
|
||||
const events: Array<ReturnType<EventFunc> & { type: string }> = []
|
||||
|
||||
pointerout()
|
||||
pointerleave()
|
||||
@@ -419,7 +430,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
let pointerdown = sendPointerdown(
|
||||
el,
|
||||
pointerEvtOptions,
|
||||
)
|
||||
) as Partial<SentEvent>
|
||||
|
||||
const pointerdownPrevented = pointerdown.preventedDefault
|
||||
const elIsDetached = $elements.isDetachedEl(el)
|
||||
@@ -461,7 +472,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
// el we just send pointerdown
|
||||
const el = mouseDownPhase.targetEl
|
||||
|
||||
if (mouseDownPhase.events.pointerdown.preventedDefault || mouseDownPhase.events.mousedown.preventedDefault || !$elements.isAttachedEl(el)) {
|
||||
if (mouseDownPhase.events.pointerdown.preventedDefault || (mouseDownPhase.events.mousedown as Partial<SentEvent>).preventedDefault || !$elements.isAttachedEl(el)) {
|
||||
return mouseDownPhase
|
||||
}
|
||||
|
||||
@@ -488,6 +499,8 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
|
||||
if (shouldMoveCursorToEndAfterMousedown(el)) {
|
||||
debug('moveSelectionToEnd due to click', el)
|
||||
// It's a curried function, so the 2 arguments are valid.
|
||||
// @ts-ignore
|
||||
$selection.moveSelectionToEnd(el, { onlyIfEmptySelection: true })
|
||||
}
|
||||
|
||||
@@ -532,7 +545,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
|
||||
const mouseDownPhase = mouse.down(fromElViewport, forceEl, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
|
||||
|
||||
const skipMouseupEvent = mouseDownPhase.events.pointerdown.skipped || mouseDownPhase.events.pointerdown.preventedDefault
|
||||
const skipMouseupEvent = mouseDownPhase.events.pointerdown.preventedDefault
|
||||
|
||||
const mouseUpPhase = mouse.up(fromElViewport, forceEl, skipMouseupEvent, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
|
||||
|
||||
@@ -638,7 +651,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
return { click }
|
||||
},
|
||||
|
||||
_contextmenuEvent (fromElViewport, forceEl, mouseEvtOptionsExtend) {
|
||||
_contextmenuEvent (fromElViewport, forceEl, mouseEvtOptionsExtend?) {
|
||||
const el = forceEl || mouse.moveToCoords(fromElViewport)
|
||||
|
||||
const win = $dom.getWindowByElement(el)
|
||||
@@ -695,7 +708,7 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
|
||||
const contextmenuEvent = mouse._contextmenuEvent(fromElViewport, forceEl)
|
||||
|
||||
const skipMouseupEvent = mouseDownPhase.events.pointerdown.skipped || mouseDownPhase.events.pointerdown.preventedDefault
|
||||
const skipMouseupEvent = mouseDownPhase.events.pointerdown.preventedDefault
|
||||
const mouseUpPhase = mouse.up(fromElViewport, forceEl, skipMouseupEvent, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
|
||||
|
||||
const clickEvents = _.extend({}, mouseDownPhase.events, mouseUpPhase.events)
|
||||
@@ -709,7 +722,14 @@ export const create = (state, keyboard, focused, Cypress) => {
|
||||
|
||||
const { stopPropagation } = window.MouseEvent.prototype
|
||||
|
||||
const sendEvent = (evtName, el, evtOptions, bubbles = false, cancelable = false, Constructor, composed = false) => {
|
||||
type SentEvent = {
|
||||
stoppedPropagation: boolean
|
||||
preventedDefault: boolean
|
||||
el: HTMLElement
|
||||
modifiers: string
|
||||
}
|
||||
|
||||
const sendEvent = (evtName, el, evtOptions, bubbles = false, cancelable = false, Constructor, composed = false): SentEvent => {
|
||||
evtOptions = _.extend({}, evtOptions, { bubbles, cancelable })
|
||||
const _eventModifiers = $Keyboard.fromModifierEventOptions(evtOptions)
|
||||
const modifiers = $Keyboard.modifiersToString(_eventModifiers)
|
||||
@@ -720,6 +740,9 @@ const sendEvent = (evtName, el, evtOptions, bubbles = false, cancelable = false,
|
||||
evt.stopPropagation = function (...args) {
|
||||
evt._hasStoppedPropagation = true
|
||||
|
||||
// stopPropagation doesn't have any arguments. So, we cannot type-safely pass the second argument.
|
||||
// But we're passing it just in case.
|
||||
// @ts-ignore
|
||||
return stopPropagation.apply(this, ...args)
|
||||
}
|
||||
}
|
||||
@@ -740,6 +763,19 @@ const formatReasonNotFired = (reason) => {
|
||||
return `⚠️ not fired (${reason})`
|
||||
}
|
||||
|
||||
type CoordsEventOptions = {
|
||||
x: number
|
||||
y: number
|
||||
clientX: number
|
||||
clientY: number
|
||||
screenX: number
|
||||
screenY: number
|
||||
pageX: number
|
||||
pageY: number
|
||||
layerX: number
|
||||
layerY: number
|
||||
}
|
||||
|
||||
const toCoordsEventOptions = (x, y, win) => {
|
||||
// these are the coords from the element's window,
|
||||
// ignoring scroll position
|
||||
|
||||
35
packages/driver/src/cy/navigation.ts
Normal file
35
packages/driver/src/cy/navigation.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { $Location } from '../cypress/location'
|
||||
|
||||
export const bothUrlsMatchAndOneHasHash = (current, remote, eitherShouldHaveHash: boolean = false): boolean => {
|
||||
// the current has a hash or the last char of href is a hash
|
||||
const currentHasHash = current.hash || current.href.slice(-1) === '#'
|
||||
// the remote has a hash or the last char of href is a hash
|
||||
const remoteHasHash = remote.hash || remote.href.slice(-1) === '#'
|
||||
|
||||
const urlHasHash = eitherShouldHaveHash ? (remoteHasHash || currentHasHash) : remoteHasHash
|
||||
|
||||
return urlHasHash &&
|
||||
// both must have the same origin
|
||||
current.origin === remote.origin &&
|
||||
// both must have the same pathname
|
||||
current.pathname === remote.pathname &&
|
||||
// both must have the same query params
|
||||
current.search === remote.search
|
||||
}
|
||||
|
||||
export const historyNavigationTriggeredHashChange = (state): boolean => {
|
||||
const delta = state('navHistoryDelta') || 0
|
||||
|
||||
if (delta === 0 || delta == null) { // page refresh
|
||||
return false
|
||||
}
|
||||
|
||||
const urls = state('urls')
|
||||
const urlPosition = state('urlPosition')
|
||||
const currentUrl = $Location.create(urls[urlPosition])
|
||||
|
||||
const nextPosition = urlPosition + delta
|
||||
const nextUrl = $Location.create(urls[nextPosition])
|
||||
|
||||
return bothUrlsMatchAndOneHasHash(currentUrl, nextUrl, true)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export function parseStaticResponseShorthand (statusCodeOrBody: number | string
|
||||
function getFixtureOpts (fixture: string): FixtureOpts {
|
||||
const [filePath, encoding] = fixture.split(',')
|
||||
|
||||
return { filePath, encoding }
|
||||
return { filePath, encoding: encoding === 'null' ? null : encoding }
|
||||
}
|
||||
|
||||
export function getBackendStaticResponse (staticResponse: Readonly<StaticResponse>): BackendStaticResponseWithArrayBuffer {
|
||||
|
||||
@@ -132,6 +132,7 @@ export const create = ($$, state) => {
|
||||
}
|
||||
|
||||
const createSnapshot = (name, $elToHighlight) => {
|
||||
Cypress.action('cy:snapshot', name)
|
||||
// create a unique selector for this el
|
||||
// but only IF the subject is truly an element. For example
|
||||
// we might be wrapping a primitive like "$([1, 2]).first()"
|
||||
|
||||
@@ -8,16 +8,24 @@ const invalidTargets = new Set(['_parent', '_top'])
|
||||
*/
|
||||
export function handleInvalidEventTarget (e: Event & {target: HTMLFormElement | HTMLAnchorElement}) {
|
||||
let targetValue = e.target.target
|
||||
let targetSet = e.target.hasAttribute('target')
|
||||
|
||||
if (invalidTargets.has(e.target.target)) {
|
||||
e.target.target = ''
|
||||
}
|
||||
|
||||
const { getAttribute, setAttribute } = e.target
|
||||
const { getAttribute, setAttribute, removeAttribute } = e.target
|
||||
const targetDescriptor = Object.getOwnPropertyDescriptor(e.target, 'target')
|
||||
|
||||
e.target.getAttribute = function (k) {
|
||||
if (k === 'target') {
|
||||
// https://github.com/cypress-io/cypress/issues/17512
|
||||
// When the target attribute doesn't exist, it should return null.
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute#non-existing_attributes
|
||||
if (!targetSet) {
|
||||
return null
|
||||
}
|
||||
|
||||
return targetValue
|
||||
}
|
||||
|
||||
@@ -26,6 +34,7 @@ export function handleInvalidEventTarget (e: Event & {target: HTMLFormElement |
|
||||
|
||||
e.target.setAttribute = function (k, v) {
|
||||
if (k === 'target') {
|
||||
targetSet = true
|
||||
targetValue = v
|
||||
|
||||
return $elements.callNativeMethod(this, 'setAttribute', 'cyTarget', v)
|
||||
@@ -34,6 +43,16 @@ export function handleInvalidEventTarget (e: Event & {target: HTMLFormElement |
|
||||
return setAttribute.call(this, k, v)
|
||||
}
|
||||
|
||||
e.target.removeAttribute = function (k) {
|
||||
if (k === 'target') {
|
||||
targetSet = false
|
||||
targetValue = ''
|
||||
|
||||
// We're not using `$elements.callNativeMethod` here because it disallows `removeAttribute`.
|
||||
return removeAttribute.call(this, k)
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetDescriptor) {
|
||||
Object.defineProperty(e.target, 'target', {
|
||||
configurable: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { validate } from '@packages/config'
|
||||
import { validate, validateNoReadOnlyConfig } from '@packages/config'
|
||||
import _ from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import * as blobUtil from 'blob-util'
|
||||
@@ -144,6 +144,24 @@ class $Cypress {
|
||||
this.state = $SetterGetter.create({})
|
||||
this.originalConfig = _.cloneDeep(config)
|
||||
this.config = $SetterGetter.create(config, (config) => {
|
||||
if (!window.top.__cySkipValidateConfig) {
|
||||
validateNoReadOnlyConfig(config, (errProperty) => {
|
||||
let errMessage
|
||||
|
||||
if (this.state('runnable')) {
|
||||
errMessage = $errUtils.errByPath('config.invalid_cypress_config_override', {
|
||||
errProperty,
|
||||
})
|
||||
} else {
|
||||
errMessage = $errUtils.errByPath('config.invalid_test_config_override', {
|
||||
errProperty,
|
||||
})
|
||||
}
|
||||
|
||||
throw new this.state('specWindow').Error(errMessage)
|
||||
})
|
||||
}
|
||||
|
||||
validate(config, (errMsg) => {
|
||||
throw new this.state('specWindow').Error(errMsg)
|
||||
})
|
||||
@@ -503,6 +521,9 @@ class $Cypress {
|
||||
case 'cy:scrolled':
|
||||
return this.emit('scrolled', ...args)
|
||||
|
||||
case 'cy:snapshot':
|
||||
return this.emit('snapshot', ...args)
|
||||
|
||||
case 'app:uncaught:exception':
|
||||
return this.emitMap('uncaught:exception', ...args)
|
||||
|
||||
|
||||
@@ -371,6 +371,14 @@ export class CommandQueue extends Queue<Command> {
|
||||
}
|
||||
|
||||
const onError = (err: Error | string) => {
|
||||
// If the runnable was marked as pending, this test was skipped
|
||||
// go ahead and just return
|
||||
const runnable = this.state('runnable')
|
||||
|
||||
if (runnable.isPending()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state('onCommandFailed')) {
|
||||
return this.state('onCommandFailed')(err, this, next)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @ts-nocheck
|
||||
|
||||
/* eslint-disable prefer-rest-params */
|
||||
import _ from 'lodash'
|
||||
import Promise from 'bluebird'
|
||||
@@ -7,14 +5,14 @@ import debugFn from 'debug'
|
||||
|
||||
import $dom from '../dom'
|
||||
import $utils from './utils'
|
||||
import $errUtils from './error_utils'
|
||||
import $errUtils, { ErrorFromProjectRejectionEvent } from './error_utils'
|
||||
import $stackUtils from './stack_utils'
|
||||
|
||||
import { create as createChai, IChai } from '../cy/chai'
|
||||
import { create as createXhr, IXhr } from '../cy/xhrs'
|
||||
import { create as createJQuery, IJQuery } from '../cy/jquery'
|
||||
import { create as createAliases, IAliases } from '../cy/aliases'
|
||||
import * as $Events from './events'
|
||||
import { extend as extendEvents } from './events'
|
||||
import { create as createEnsures, IEnsures } from '../cy/ensures'
|
||||
import { create as createFocused, IFocused } from '../cy/focused'
|
||||
import { create as createMouse, Mouse } from '../cy/mouse'
|
||||
@@ -33,6 +31,8 @@ import { CommandQueue } from './command_queue'
|
||||
import { initVideoRecorder } from '../cy/video-recorder'
|
||||
import { TestConfigOverride } from '../cy/testConfigOverrides'
|
||||
import { create as createOverrides, IOverrides } from '../cy/overrides'
|
||||
import { historyNavigationTriggeredHashChange } from '../cy/navigation'
|
||||
import { EventEmitter2 } from 'eventemitter2'
|
||||
|
||||
const debugErrors = debugFn('cypress:driver:errors')
|
||||
|
||||
@@ -54,7 +54,7 @@ function __stackReplacementMarker (fn, ctx, args) {
|
||||
return fn.apply(ctx, args)
|
||||
}
|
||||
|
||||
declare let top: WindowProxy & { __alreadySetErrorHandlers__: boolean } | null
|
||||
declare let top: WindowProxy & { __alreadySetErrorHandlers__: boolean }
|
||||
|
||||
// We only set top.onerror once since we make it configurable:false
|
||||
// but we update cy instance every run (page reload or rerun button)
|
||||
@@ -80,7 +80,7 @@ const setTopOnError = function (Cypress, cy: $Cy) {
|
||||
|
||||
// eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces
|
||||
const onTopError = (handlerType) => (event) => {
|
||||
const { originalErr, err, promise } = $errUtils.errorFromUncaughtEvent(handlerType, event)
|
||||
const { originalErr, err, promise } = $errUtils.errorFromUncaughtEvent(handlerType, event) as ErrorFromProjectRejectionEvent
|
||||
|
||||
// in some callbacks like for cy.intercept, we catch the errors and then
|
||||
// rethrow them, causing them to get caught by the top frame
|
||||
@@ -118,7 +118,7 @@ const setTopOnError = function (Cypress, cy: $Cy) {
|
||||
top.__alreadySetErrorHandlers__ = true
|
||||
}
|
||||
|
||||
export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuery, ILocation, ITimer, IChai, IXhr, IAliases, IEnsures, ISnapshots, IFocused {
|
||||
export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssertions, IRetries, IJQuery, ILocation, ITimer, IChai, IXhr, IAliases, IEnsures, ISnapshots, IFocused {
|
||||
id: string
|
||||
specWindow: any
|
||||
state: any
|
||||
@@ -138,6 +138,8 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
|
||||
isStable: IStability['isStable']
|
||||
whenStable: IStability['whenStable']
|
||||
isAnticipatingMultidomain: IStability['isAnticipatingMultidomain']
|
||||
whenStableOrAnticipatingMultidomain: IStability['whenStableOrAnticipatingMultidomain']
|
||||
|
||||
assert: IAssertions['assert']
|
||||
verifyUpcomingAssertions: IAssertions['verifyUpcomingAssertions']
|
||||
@@ -207,6 +209,8 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
private commandFns: Record<string, Function> = {}
|
||||
|
||||
constructor (specWindow, Cypress, Cookies, state, config, autoRun = true) {
|
||||
super()
|
||||
|
||||
state('specWindow', specWindow)
|
||||
|
||||
this.specWindow = specWindow
|
||||
@@ -248,7 +252,7 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
this.isStable = stability.isStable
|
||||
this.whenStable = stability.whenStable
|
||||
this.isAnticipatingMultidomain = stability.isAnticipatingMultidomain
|
||||
this.whenAnticipatingMultidomain = stability.whenAnticipatingMultidomain
|
||||
this.whenStableOrAnticipatingMultidomain = stability.whenStableOrAnticipatingMultidomain
|
||||
|
||||
const assertions = createAssertions(Cypress, this)
|
||||
|
||||
@@ -256,7 +260,7 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
this.verifyUpcomingAssertions = assertions.verifyUpcomingAssertions
|
||||
|
||||
const onFinishAssertions = function () {
|
||||
return assertions.finishAssertions.apply(window, arguments)
|
||||
return assertions.finishAssertions.apply(window, arguments as any)
|
||||
}
|
||||
|
||||
const retries = createRetries(Cypress, state, this.timeout, this.clearTimeout, this.whenStable, onFinishAssertions)
|
||||
@@ -353,7 +357,7 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
// make cy global in the specWindow
|
||||
specWindow.cy = this
|
||||
|
||||
$Events.extend(this)
|
||||
extendEvents(this)
|
||||
|
||||
Cypress.on('enqueue:command', (attrs) => {
|
||||
this.enqueue(attrs)
|
||||
@@ -368,7 +372,7 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
return this.queue.stopped
|
||||
}
|
||||
|
||||
fail (err, options = {}) {
|
||||
fail (err, options: { async?: boolean } = {}) {
|
||||
// this means the error has already been through this handler and caught
|
||||
// again. but we don't need to run it through again, so we can re-throw
|
||||
// it and it will fail the test as-is
|
||||
@@ -450,7 +454,7 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
try {
|
||||
// collect all of the callbacks for 'fail'
|
||||
rets = this.Cypress.action('cy:fail', err, this.state('runnable'))
|
||||
} catch (cyFailErr) {
|
||||
} catch (cyFailErr: any) {
|
||||
// and if any of these throw synchronously immediately error
|
||||
cyFailErr.isCyFailErr = true
|
||||
|
||||
@@ -488,8 +492,19 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
// proxy has not injected Cypress.action('window:before:load')
|
||||
// so Cypress.onBeforeAppWindowLoad() was never called
|
||||
return $autIframe.on('load', () => {
|
||||
// if setting these props failed
|
||||
// then we know we're in a cross origin failure
|
||||
if (historyNavigationTriggeredHashChange(this.state)) {
|
||||
// Skip load event.
|
||||
// Chromium 97+ triggers fires iframe onload for cross-origin-initiated same-document
|
||||
// navigations to make it appear to be a cross-document navigation, even when it wasn't
|
||||
// to alleviate security risk where a cross-origin initiator can check whether
|
||||
// or not onload fired to guess the url of a target frame.
|
||||
// When the onload is fired, neither the before:unload or unload event is fired to remove
|
||||
// the attached listeners or to clean up the current page state.
|
||||
// https://github.com/cypress-io/cypress/issues/19230
|
||||
return
|
||||
}
|
||||
|
||||
// if setting these props failed then we know we're in a cross origin failure
|
||||
try {
|
||||
const autWindow = getContentWindow($autIframe)
|
||||
|
||||
@@ -835,6 +850,8 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
r(err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setRunnable (runnable, hookId) {
|
||||
@@ -962,6 +979,12 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
// else just return ret
|
||||
return ret
|
||||
} catch (err) {
|
||||
// If the runnable was marked as pending, this test was skipped
|
||||
// go ahead and just return
|
||||
if (runnable.isPending()) {
|
||||
return
|
||||
}
|
||||
|
||||
// if runnable.fn threw synchronously, then it didnt fail from
|
||||
// a cypress command, but we should still teardown and handle
|
||||
// the error
|
||||
@@ -1018,7 +1041,7 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
$Listeners.bindTo(contentWindow, {
|
||||
// eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces
|
||||
onError: (handlerType) => (event) => {
|
||||
const { originalErr, err, promise } = $errUtils.errorFromUncaughtEvent(handlerType, event)
|
||||
const { originalErr, err, promise } = $errUtils.errorFromUncaughtEvent(handlerType, event) as ErrorFromProjectRejectionEvent
|
||||
const handled = cy.onUncaughtException({
|
||||
err,
|
||||
promise,
|
||||
@@ -1034,6 +1057,9 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
// uncaught exception behavior (logging to console)
|
||||
return undefined
|
||||
},
|
||||
onHistoryNav (delta) {
|
||||
cy.state('navHistoryDelta', delta)
|
||||
},
|
||||
onSubmit (e) {
|
||||
return cy.Cypress.action('app:form:submitted', e)
|
||||
},
|
||||
@@ -1106,7 +1132,8 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
return this.Cypress.action('cy:command:enqueued', obj)
|
||||
}
|
||||
|
||||
private getCommandsUntilFirstParentOrValidSubject (command, memo = []) {
|
||||
// TODO: Replace any with Command type.
|
||||
private getCommandsUntilFirstParentOrValidSubject (command, memo: any[] = []) {
|
||||
if (!command) {
|
||||
return null
|
||||
}
|
||||
@@ -1122,11 +1149,12 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
return this.getCommandsUntilFirstParentOrValidSubject(command.get('prev'), memo)
|
||||
}
|
||||
|
||||
private pushSubjectAndValidate (name, args, firstCall, prevSubject) {
|
||||
// TODO: make string[] more
|
||||
private pushSubjectAndValidate (name, args, firstCall, prevSubject: string[]) {
|
||||
if (firstCall) {
|
||||
// if we have a prevSubject then error
|
||||
// since we're invoking this improperly
|
||||
if (prevSubject && ![].concat(prevSubject).includes('optional')) {
|
||||
if (prevSubject && !([] as string[]).concat(prevSubject).includes('optional')) {
|
||||
const stringifiedArg = $utils.stringifyActual(args[0])
|
||||
|
||||
$errUtils.throwErrByPath('miscellaneous.invoking_child_without_parent', {
|
||||
@@ -1148,7 +1176,7 @@ export class $Cy implements ITimeouts, IStability, IAssertions, IRetries, IJQuer
|
||||
if (prevSubject) {
|
||||
// make sure our current subject is valid for
|
||||
// what we expect in this command
|
||||
this.ensureSubjectByType(subject, prevSubject, name)
|
||||
this.ensureSubjectByType(subject, prevSubject)
|
||||
}
|
||||
|
||||
args.unshift(subject)
|
||||
|
||||
@@ -250,10 +250,16 @@ export default {
|
||||
message: `Setting the config via ${cmd('Cypress.config')} failed with the following validation error:\n\n{{errMsg}}`,
|
||||
docsUrl: 'https://on.cypress.io/config',
|
||||
},
|
||||
'invalid_test_override': {
|
||||
invalid_test_override: {
|
||||
message: `The config override passed to your test has the following validation error:\n\n{{errMsg}}`,
|
||||
docsUrl: 'https://on.cypress.io/config',
|
||||
},
|
||||
invalid_cypress_config_override: {
|
||||
message: `\`Cypress.config()\` cannot mutate option \`{{errProperty}}\` because it is a read-only property.`,
|
||||
},
|
||||
invalid_test_config_override: {
|
||||
message: `Cypress test configuration cannot mutate option \`{{errProperty}}\` because it is a read-only property.`,
|
||||
},
|
||||
},
|
||||
|
||||
contains: {
|
||||
|
||||
@@ -265,6 +265,7 @@ export class InternalCypressError extends Error {
|
||||
|
||||
export class CypressError extends Error {
|
||||
docsUrl?: string
|
||||
retry?: boolean
|
||||
|
||||
constructor (message) {
|
||||
super(message)
|
||||
@@ -469,7 +470,12 @@ const convertErrorEventPropertiesToObject = (args) => {
|
||||
})
|
||||
}
|
||||
|
||||
const errorFromErrorEvent = (event) => {
|
||||
export interface ErrorFromErrorEvent {
|
||||
originalErr: Error
|
||||
err: Error
|
||||
}
|
||||
|
||||
const errorFromErrorEvent = (event): ErrorFromErrorEvent => {
|
||||
let { message, filename, lineno, colno, error } = event
|
||||
let docsUrl = error?.docsUrl
|
||||
|
||||
@@ -497,7 +503,11 @@ const errorFromErrorEvent = (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const errorFromProjectRejectionEvent = (event) => {
|
||||
export interface ErrorFromProjectRejectionEvent extends ErrorFromErrorEvent {
|
||||
promise: Promise<any>
|
||||
}
|
||||
|
||||
const errorFromProjectRejectionEvent = (event): ErrorFromProjectRejectionEvent => {
|
||||
// Bluebird triggers "unhandledrejection" with its own custom error event
|
||||
// where the `promise` and `reason` are attached to event.detail
|
||||
// http://bluebirdjs.com/docs/api/error-management-configuration.html
|
||||
|
||||
@@ -135,7 +135,7 @@ const getCodeFrameFromSource = (sourceCode, { line, column, relativeFile, absolu
|
||||
}
|
||||
}
|
||||
|
||||
const captureUserInvocationStack = (ErrorConstructor, userInvocationStack) => {
|
||||
const captureUserInvocationStack = (ErrorConstructor, userInvocationStack?) => {
|
||||
if (!userInvocationStack) {
|
||||
const newErr = new ErrorConstructor('userInvocationStack')
|
||||
|
||||
|
||||
@@ -44,10 +44,10 @@ export const isAttached = function ($el) {
|
||||
})
|
||||
}
|
||||
|
||||
export const isDetachedEl = (el: HTMLElement) => {
|
||||
export const isDetachedEl = (el: HTMLElement | JQuery<any>) => {
|
||||
return !isAttachedEl(el)
|
||||
}
|
||||
|
||||
export const isAttachedEl = function (el: HTMLElement) {
|
||||
export const isAttachedEl = function (el: HTMLElement | JQuery<any>) {
|
||||
return isAttached($(el))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import $document from '../document'
|
||||
import $jquery from '../jquery'
|
||||
import { getTagName } from './elementHelpers'
|
||||
import { isWithinShadowRoot, getShadowElementFromPoint } from './shadow'
|
||||
import { normalizeWhitespaces, escapeQuotes, isSelector } from './utils'
|
||||
import { normalizeWhitespaces, escapeQuotes } from './utils'
|
||||
|
||||
/**
|
||||
* Find Parents relative to an initial element
|
||||
@@ -125,10 +125,16 @@ export const getFirstDeepestElement = ($el: JQuery, index = 0) => {
|
||||
const $current = $el.slice(index, index + 1)
|
||||
const $next = $el.slice(index + 1, index + 2)
|
||||
|
||||
if (!$next) {
|
||||
if (!$next || $current.length === 0) {
|
||||
return $current
|
||||
}
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/14861
|
||||
// filter out the <script> and <style> tags
|
||||
if ($current && ['SCRIPT', 'STYLE'].includes($current.prop('tagName'))) {
|
||||
return getFirstDeepestElement($el, index + 1)
|
||||
}
|
||||
|
||||
// does current contain next?
|
||||
if ($.contains($current.get(0), $next.get(0))) {
|
||||
return getFirstDeepestElement($el, index + 1)
|
||||
@@ -217,20 +223,6 @@ export const getElements = ($el) => {
|
||||
return els
|
||||
}
|
||||
|
||||
// Remove <style> and <script> elements inside <body>. Even though the contains
|
||||
// selector avoids selecting them with :not(script,style), it will find the
|
||||
// text anyway and attribute it to the <body>
|
||||
// https://github.com/cypress-io/cypress/issues/14861
|
||||
const removeScriptAndStyleElements = (elem) => {
|
||||
const $elem = $(elem)
|
||||
|
||||
if (!isSelector($elem, 'body')) return elem
|
||||
|
||||
$elem.find('script,style').remove()
|
||||
|
||||
return $elem[0]
|
||||
}
|
||||
|
||||
export const getContainsSelector = (text, filter = '', options: {
|
||||
matchCase?: boolean
|
||||
} = {}) => {
|
||||
@@ -252,14 +244,12 @@ export const getContainsSelector = (text, filter = '', options: {
|
||||
|
||||
// taken from jquery's normal contains method
|
||||
cyContainsSelector = function (elem) {
|
||||
elem = removeScriptAndStyleElements(elem)
|
||||
let testText = normalizeWhitespaces(elem)
|
||||
const testText = normalizeWhitespaces(elem)
|
||||
|
||||
return text.test(testText)
|
||||
}
|
||||
} else if (_.isString(text)) {
|
||||
cyContainsSelector = function (elem) {
|
||||
elem = removeScriptAndStyleElements(elem)
|
||||
let testText = normalizeWhitespaces(elem)
|
||||
|
||||
if (!options.matchCase) {
|
||||
@@ -284,7 +274,7 @@ export const getContainsSelector = (text, filter = '', options: {
|
||||
const textToFind = escapedText.includes(`\'`) ? `"${escapedText}"` : `'${escapedText}'`
|
||||
|
||||
// use custom cy-contains selector that is registered above
|
||||
return `${filter}:not(script,style):cy-contains(${textToFind}), ${filter}[type='submit'][value~=${textToFind}]`
|
||||
return `${filter}:cy-contains(${textToFind}), ${filter}[type='submit'][value~=${textToFind}]`
|
||||
})
|
||||
|
||||
return selectors.join()
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "15.2.0",
|
||||
"electron-packager": "14.1.1",
|
||||
"electron-packager": "15.1.0",
|
||||
"execa": "4.1.0",
|
||||
"mocha": "3.5.3"
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
} from './external-types'
|
||||
|
||||
export type FixtureOpts = {
|
||||
encoding: string
|
||||
encoding: string | null
|
||||
filePath: string
|
||||
}
|
||||
|
||||
|
||||
@@ -728,9 +728,14 @@ describe('errors ui', () => {
|
||||
})
|
||||
|
||||
describe('docs url', () => {
|
||||
after(() => {
|
||||
window.top.__cySkipValidateConfig = false
|
||||
})
|
||||
|
||||
const file = 'docs_url_spec.js'
|
||||
const docsUrl = 'https://on.cypress.io/viewport'
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
verify.it('displays as button in interactive mode', { retries: 1 }, {
|
||||
file,
|
||||
verifyFn () {
|
||||
|
||||
@@ -401,8 +401,13 @@ describe('runner/cypress retries.ui.spec', { viewportWidth: 600, viewportHeight:
|
||||
})
|
||||
|
||||
describe('can configure retries', () => {
|
||||
after(() => {
|
||||
window.top.__cySkipValidateConfig = false
|
||||
})
|
||||
|
||||
const haveCorrectError = ($el) => cy.wrap($el).last().parentsUntil('.collapsible').last().parent().find('.runnable-err').should('contain', 'Unspecified AssertionError')
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
it('via config value', () => {
|
||||
runIsolatedCypress({
|
||||
suites: {
|
||||
|
||||
@@ -520,13 +520,13 @@ export = {
|
||||
const originalBrowserKill = launchedBrowser.kill
|
||||
|
||||
/* @ts-expect-error */
|
||||
launchedBrowser.kill = async (...args) => {
|
||||
launchedBrowser.kill = (...args) => {
|
||||
debug('closing remote interface client')
|
||||
|
||||
await criClient.close()
|
||||
criClient.close()
|
||||
debug('closing chrome')
|
||||
|
||||
await originalBrowserKill.apply(launchedBrowser, args)
|
||||
originalBrowserKill.apply(launchedBrowser, args)
|
||||
}
|
||||
|
||||
await this._maybeRecordVideo(criClient, options, browser.majorVersion)
|
||||
|
||||
@@ -191,7 +191,6 @@ export const create = Bluebird.method((target: websocketUrl, onAsynchronousError
|
||||
maybeDebugCdpMessages(cri)
|
||||
|
||||
cri.send = Bluebird.promisify(cri.send, { context: cri })
|
||||
cri.close = Bluebird.promisify(cri.close, { context: cri })
|
||||
|
||||
// @see https://github.com/cyrus-and/chrome-remote-interface/issues/72
|
||||
cri._notifier.on('disconnect', reconnect)
|
||||
|
||||
@@ -165,7 +165,7 @@ module.exports = {
|
||||
|
||||
return obj
|
||||
}).catch((err) => {
|
||||
throw new Error(`'${fixture}' is not a valid JavaScript object.${err.toString()}`)
|
||||
throw new Error(`'${fixture}' is not a valid JavaScript object.\n${err.toString()}`)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -190,10 +190,16 @@ module.exports = {
|
||||
parseHtml (p, fixture) {
|
||||
return fs.readFileAsync(p, 'utf8')
|
||||
.bind(this)
|
||||
.catch((err) => {
|
||||
throw new Error(`Unable to parse '${fixture}'.\n${err.toString()}`)
|
||||
})
|
||||
},
|
||||
|
||||
parse (p, fixture, encoding) {
|
||||
return fs.readFileAsync(p, encoding)
|
||||
.bind(this)
|
||||
.catch((err) => {
|
||||
throw new Error(`Unable to parse '${fixture}'.\n${err.toString()}`)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -300,10 +300,9 @@ describe('lib/browsers/chrome', () => {
|
||||
.then(() => {
|
||||
expect(this.launchedBrowser.kill).to.be.a('function')
|
||||
|
||||
return this.launchedBrowser.kill()
|
||||
}).then(() => {
|
||||
expect(this.criClient.close).to.be.calledOnce
|
||||
this.launchedBrowser.kill()
|
||||
|
||||
expect(this.criClient.close).to.be.calledOnce
|
||||
expect(kill).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1430,6 +1430,7 @@ describe('lib/config', () => {
|
||||
e2e: { from: 'default', value: {} },
|
||||
env: {},
|
||||
execTimeout: { value: 60000, from: 'default' },
|
||||
exit: { value: true, from: 'default' },
|
||||
experimentalFetchPolyfill: { value: false, from: 'default' },
|
||||
experimentalInteractiveRunEvents: { value: false, from: 'default' },
|
||||
experimentalSourceRewriting: { value: false, from: 'default' },
|
||||
@@ -1441,6 +1442,8 @@ describe('lib/config', () => {
|
||||
ignoreTestFiles: { value: '*.hot-update.js', from: 'default' },
|
||||
includeShadowDom: { value: false, from: 'default' },
|
||||
integrationFolder: { value: 'cypress/integration', from: 'default' },
|
||||
isInteractive: { value: true, from: 'default' },
|
||||
keystrokeDelay: { value: 0, from: 'default' },
|
||||
modifyObstructiveCode: { value: true, from: 'default' },
|
||||
numTestsKeptInMemory: { value: 50, from: 'default' },
|
||||
pageLoadTimeout: { value: 60000, from: 'default' },
|
||||
@@ -1517,6 +1520,7 @@ describe('lib/config', () => {
|
||||
downloadsFolder: { value: 'cypress/downloads', from: 'default' },
|
||||
e2e: { from: 'default', value: {} },
|
||||
execTimeout: { value: 60000, from: 'default' },
|
||||
exit: { value: true, from: 'default' },
|
||||
experimentalFetchPolyfill: { value: false, from: 'default' },
|
||||
experimentalInteractiveRunEvents: { value: false, from: 'default' },
|
||||
experimentalSourceRewriting: { value: false, from: 'default' },
|
||||
@@ -1550,6 +1554,8 @@ describe('lib/config', () => {
|
||||
ignoreTestFiles: { value: '*.hot-update.js', from: 'default' },
|
||||
includeShadowDom: { value: false, from: 'default' },
|
||||
integrationFolder: { value: 'cypress/integration', from: 'default' },
|
||||
isInteractive: { value: true, from: 'default' },
|
||||
keystrokeDelay: { value: 0, from: 'default' },
|
||||
modifyObstructiveCode: { value: true, from: 'default' },
|
||||
numTestsKeptInMemory: { value: 50, from: 'default' },
|
||||
pageLoadTimeout: { value: 60000, from: 'default' },
|
||||
|
||||
@@ -215,7 +215,7 @@ ParseError: Unterminated string constant\
|
||||
.then(() => {
|
||||
throw new Error('should have failed but did not')
|
||||
}).catch((err) => {
|
||||
expect(err.message).to.eq(`'bad_js.js' is not a valid JavaScript object.\n${e}`)
|
||||
expect(err.message).to.eq(`'bad_js.js' is not a valid JavaScript object.\n\n${e}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,32 +1,49 @@
|
||||
/* eslint-disable no-console */
|
||||
const minimist = require('minimist')
|
||||
const options = minimist(process.argv)
|
||||
const os = require('os')
|
||||
const la = require('lazy-ass')
|
||||
const fs = require('fs-extra')
|
||||
const is = require('check-more-types')
|
||||
const execa = require('execa')
|
||||
const { getNameAndBinary } = require('./utils')
|
||||
|
||||
/* eslint-disable no-console */
|
||||
const options = minimist(process.argv)
|
||||
|
||||
const cwd = options.cwd || '/tmp/testing'
|
||||
|
||||
fs.ensureDirSync(cwd)
|
||||
|
||||
const spawnOpts = {
|
||||
cwd,
|
||||
shell: os.platform() === 'win32' ? 'bash.exe' : '/bin/bash',
|
||||
stdio: 'inherit',
|
||||
}
|
||||
const { npm, binary } = getNameAndBinary(process.argv)
|
||||
|
||||
la(is.unemptyString(npm), 'missing npm url')
|
||||
la(is.unemptyString(binary), 'missing binary url')
|
||||
|
||||
console.log('testing NPM from', npm)
|
||||
console.log('and binary from', binary)
|
||||
const cwd = options.cwd || process.cwd()
|
||||
|
||||
console.log('in', cwd)
|
||||
|
||||
execa(`npm install ${npm}`, {
|
||||
cwd,
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
CYPRESS_INSTALL_BINARY: binary,
|
||||
},
|
||||
})
|
||||
console.log('Create Dummy Project')
|
||||
execa('npm init -y', spawnOpts)
|
||||
.then(console.log)
|
||||
.then(() => {
|
||||
console.log('testing NPM from', npm)
|
||||
console.log('and binary from', binary)
|
||||
console.log('in', cwd)
|
||||
|
||||
return execa(`npm install ${npm}`, {
|
||||
...spawnOpts,
|
||||
env: {
|
||||
CYPRESS_INSTALL_BINARY: binary,
|
||||
},
|
||||
}).then(console.log)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Verify Cypress binary')
|
||||
|
||||
return execa('$(yarn bin cypress) verify', spawnOpts)
|
||||
.then(console.log)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
|
||||
70
system-tests/__snapshots__/issue_6407_spec.js
Normal file
70
system-tests/__snapshots__/issue_6407_spec.js
Normal file
@@ -0,0 +1,70 @@
|
||||
exports['e2e issue 6407 throws if mutating read-only config with test configuration 1'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (issue_6407_spec.js) │
|
||||
│ Searched: cypress/integration/issue_6407_spec.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: issue_6407_spec.js (1 of 1)
|
||||
|
||||
|
||||
1) throws if mutating read-only config with test configuration
|
||||
|
||||
0 passing
|
||||
1 failing
|
||||
|
||||
1) throws if mutating read-only config with test configuration:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
CypressError: Cypress test configuration cannot mutate option \`chromeWebSecurity\` because it is a read-only property.
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 1 │
|
||||
│ Passing: 0 │
|
||||
│ Failing: 1 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 0 │
|
||||
│ Video: true │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: issue_6407_spec.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
(Video)
|
||||
|
||||
- Started processing: Compressing to 32 CRF
|
||||
- Finished processing: /XXX/XXX/XXX/cypress/videos/issue_6407_spec.js.mp4 (X second)
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ issue_6407_spec.js XX:XX 1 - 1 - - │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 1 - 1 - -
|
||||
|
||||
|
||||
`
|
||||
@@ -1,15 +1,15 @@
|
||||
import fs from 'fs-extra'
|
||||
import _path from 'path'
|
||||
import chokidar from 'chokidar'
|
||||
import os from 'os'
|
||||
import cachedir from 'cachedir'
|
||||
import execa from 'execa'
|
||||
import tempDir from 'temp-dir'
|
||||
|
||||
const root = _path.join(__dirname, '..')
|
||||
|
||||
const serverRoot = _path.join(__dirname, '../../packages/server/')
|
||||
const projects = _path.join(root, 'projects')
|
||||
const tmpDir = _path.join(os.tmpdir(), 'cy-projects')
|
||||
const cyTmpDir = _path.join(tempDir, 'cy-projects')
|
||||
|
||||
// copy contents instead of deleting+creating new file, which can cause
|
||||
// filewatchers to lose track of toFile.
|
||||
@@ -27,9 +27,9 @@ const copyContents = (fromFile, toFile) => {
|
||||
}
|
||||
|
||||
// copies all of the project fixtures
|
||||
// to the tmpDir .projects in the root
|
||||
// to the cyTmpDir .projects in the root
|
||||
export function scaffold () {
|
||||
fs.copySync(projects, tmpDir)
|
||||
fs.copySync(projects, cyTmpDir)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ export function scaffold () {
|
||||
*/
|
||||
export function scaffoldProject (project: string): void {
|
||||
const from = _path.join(projects, project)
|
||||
const to = _path.join(tmpDir, project)
|
||||
const to = _path.join(cyTmpDir, project)
|
||||
|
||||
fs.copySync(from, to)
|
||||
}
|
||||
@@ -131,7 +131,7 @@ function getYarnCommand (opts: {
|
||||
|
||||
// in CircleCI, this offline cache can be used
|
||||
if (opts.isCI) cmd += ` --cache-folder=~/.yarn-${process.platform} `
|
||||
else cmd += ` --cache-folder=${_path.join(os.tmpdir(), 'cy-system-tests-yarn-cache', String(Date.now()))}`
|
||||
else cmd += ` --cache-folder=${_path.join(tempDir, 'cy-system-tests-yarn-cache', String(Date.now()))}`
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -291,7 +291,7 @@ export async function scaffoldCommonNodeModules () {
|
||||
}
|
||||
|
||||
export async function symlinkNodeModule (pkg) {
|
||||
const from = _path.join(tmpDir, 'node_modules', pkg)
|
||||
const from = _path.join(cyTmpDir, 'node_modules', pkg)
|
||||
const to = pathToPackage(pkg)
|
||||
|
||||
await fs.ensureDir(_path.dirname(from))
|
||||
@@ -312,7 +312,7 @@ export function scaffoldWatch () {
|
||||
chokidar.watch(watchdir, {
|
||||
})
|
||||
.on('change', (srcFilepath, stats) => {
|
||||
const tmpFilepath = _path.join(tmpDir, _path.relative(watchdir, srcFilepath))
|
||||
const tmpFilepath = _path.join(cyTmpDir, _path.relative(watchdir, srcFilepath))
|
||||
|
||||
return copyContents(srcFilepath, tmpFilepath)
|
||||
})
|
||||
@@ -320,19 +320,19 @@ export function scaffoldWatch () {
|
||||
}
|
||||
|
||||
// removes all of the project fixtures
|
||||
// from the tmpDir .projects in the root
|
||||
// from the cyTmpDir .projects in the root
|
||||
export function remove () {
|
||||
return fs.removeSync(tmpDir)
|
||||
return fs.removeSync(cyTmpDir)
|
||||
}
|
||||
|
||||
// returns the path to project fixture
|
||||
// in the tmpDir
|
||||
// in the cyTmpDir
|
||||
export function project (...args) {
|
||||
return this.projectPath.apply(this, args)
|
||||
}
|
||||
|
||||
export function projectPath (name) {
|
||||
return _path.join(tmpDir, name)
|
||||
return _path.join(cyTmpDir, name)
|
||||
}
|
||||
|
||||
export function get (fixture, encoding: BufferEncoding = 'utf8') {
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"ssestream": "1.0.1",
|
||||
"supertest": "4.0.2",
|
||||
"systeminformation": "5.6.4",
|
||||
"temp-dir": "^2.0.0",
|
||||
"webpack": "4.43.0",
|
||||
"ws": "5.2.3"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/* eslint-disable mocha/no-global-tests, no-undef */
|
||||
it('throws if mutating read-only config with test configuration', { chromeWebSecurity: false }, () => {
|
||||
expect(true)
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference types="cypress" />
|
||||
window.top.__cySkipValidateConfig = true
|
||||
Cypress.config('isInteractive', true)
|
||||
Cypress.config('experimentalSessionSupport', true)
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { fail, verify } from '../../e2e/cypress/support/util'
|
||||
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
|
||||
@@ -16,7 +18,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 15,
|
||||
line: 17,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
/**
|
||||
* This tests the error UI for a certain webpack preprocessor setup.
|
||||
* It does this by having a test fail and then a subsequent test run that
|
||||
@@ -16,6 +15,8 @@ import { fail, verify } from '../../../e2e/cypress/support/util'
|
||||
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
// @ts-ignore
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
@@ -26,7 +27,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 25,
|
||||
line: 26,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
/**
|
||||
* This tests the error UI for a certain webpack preprocessor setup.
|
||||
* It does this by having a test fail and then a subsequent test run that
|
||||
@@ -16,6 +15,8 @@ import { fail, verify } from '../../../e2e/cypress/support/util'
|
||||
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
// @ts-ignore
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
@@ -26,7 +27,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 25,
|
||||
line: 26,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
|
||||
import { fail, verify } from '../../../e2e/cypress/support/util'
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
|
||||
@@ -16,7 +19,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 15,
|
||||
line: 18,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
14
system-tests/test/issue_6407_spec.js
Normal file
14
system-tests/test/issue_6407_spec.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const systemTests = require('../lib/system-tests').default
|
||||
|
||||
describe('e2e issue 6407', () => {
|
||||
systemTests.setup()
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/6407
|
||||
it('throws if mutating read-only config with test configuration', function () {
|
||||
return systemTests.exec(this, {
|
||||
spec: 'issue_6407_spec.js',
|
||||
snapshot: true,
|
||||
expectedExitCode: 1,
|
||||
})
|
||||
})
|
||||
})
|
||||
164
yarn.lock
164
yarn.lock
@@ -10799,22 +10799,7 @@ asap@^2.0.0, asap@~2.0.3, asap@~2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
asar@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/asar/-/asar-2.1.0.tgz#97c6a570408c4e38a18d4a3fb748a621b5a7844e"
|
||||
integrity sha512-d2Ovma+bfqNpvBzY/KU8oPY67ZworixTpkjSx0PCXnQi67c2cXmssaTxpFDUM0ttopXoGx/KRxNg/GDThYbXQA==
|
||||
dependencies:
|
||||
chromium-pickle-js "^0.2.0"
|
||||
commander "^2.20.0"
|
||||
cuint "^0.2.2"
|
||||
glob "^7.1.3"
|
||||
minimatch "^3.0.4"
|
||||
mkdirp "^0.5.1"
|
||||
tmp-promise "^1.0.5"
|
||||
optionalDependencies:
|
||||
"@types/glob" "^7.1.1"
|
||||
|
||||
asar@^3.0.3:
|
||||
asar@^3.0.0, asar@^3.0.3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473"
|
||||
integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==
|
||||
@@ -11145,14 +11130,6 @@ aws4@^1.8.0:
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||
|
||||
axios@0.18.1:
|
||||
version "0.18.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
|
||||
integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==
|
||||
dependencies:
|
||||
follow-redirects "1.5.10"
|
||||
is-buffer "^2.0.2"
|
||||
|
||||
axios@0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
|
||||
@@ -11161,12 +11138,12 @@ axios@0.19.0:
|
||||
follow-redirects "1.5.10"
|
||||
is-buffer "^2.0.2"
|
||||
|
||||
axios@0.19.2:
|
||||
version "0.19.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
||||
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
|
||||
axios@0.21.2:
|
||||
version "0.21.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
|
||||
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
|
||||
dependencies:
|
||||
follow-redirects "1.5.10"
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
axobject-query@2.0.2:
|
||||
version "2.0.2"
|
||||
@@ -15370,13 +15347,6 @@ cross-spawn@^5.0.1:
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-zip@^2.1.5:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-zip/-/cross-zip-2.1.6.tgz#344d3ba9488609942987d815bb84860cff3d9491"
|
||||
integrity sha512-xLIETNkzRcU6jGRzenJyRFxahbtP4628xEKMTI/Ql0Vu8m4h8M7uRLVi7E5OYHuJ6VQPsG4icJumKAFUvfm0+A==
|
||||
dependencies:
|
||||
rimraf "^3.0.0"
|
||||
|
||||
crypt@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||
@@ -15905,11 +15875,6 @@ csstype@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b"
|
||||
integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==
|
||||
|
||||
cuint@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
|
||||
integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=
|
||||
|
||||
currently-unhandled@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
||||
@@ -17390,15 +17355,7 @@ electron-is-dev@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-2.0.0.tgz#833487a069b8dad21425c67a19847d9064ab19bd"
|
||||
integrity sha512-3X99K852Yoqu9AcW50qz3ibYBWY79/pBhlMCab8ToEWS48R0T9tyxRiQhwylE7zQdXrMnx2JKqUJyMPmt5FBqA==
|
||||
|
||||
electron-notarize@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-0.2.1.tgz#759e8006decae19134f82996ed910db26d9192cc"
|
||||
integrity sha512-oZ6/NhKeXmEKNROiFmRNfytqu3cxqC95sjooG7kBXQVEUSQkZnbiAhxVh5jXngL881G197pbwpeVPJyM7Ikmxw==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
fs-extra "^8.1.0"
|
||||
|
||||
electron-notarize@^1.1.1:
|
||||
electron-notarize@^1.0.0, electron-notarize@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-1.1.1.tgz#3ed274b36158c1beb1dbef14e7faf5927e028629"
|
||||
integrity sha512-kufsnqh86CTX89AYNG3NCPoboqnku/+32RxeJ2+7A4Rbm4bbOx0Nc7XTy3/gAlBfpj9xPAxHfhZLOHgfi6cJVw==
|
||||
@@ -17430,18 +17387,19 @@ electron-osx-sign@^0.5.0:
|
||||
minimist "^1.2.0"
|
||||
plist "^3.0.1"
|
||||
|
||||
electron-packager@14.1.1:
|
||||
version "14.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-14.1.1.tgz#260affa0287070e1cf25e5fed074564b8c5494ed"
|
||||
integrity sha512-PODWJ8LFfwUCniTMy4Z5iiZyVHi4W71Pvn/SxJPC6kbI3EfZvo8n5H856XATxNUGVxlmAB5qeSbRym8/f9jISg==
|
||||
electron-packager@15.1.0:
|
||||
version "15.1.0"
|
||||
resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-15.1.0.tgz#16a3733e4cad26112a2ac36f0b0f35c3b0170eff"
|
||||
integrity sha512-THNm4bz1DfvR9f0g51+NjuAYELflM8+1vhQ/iv/G8vyZNKzSMuFd5doobngQKq3rRsLdPNZVnGqDdgS884d7Og==
|
||||
dependencies:
|
||||
"@electron/get" "^1.6.0"
|
||||
asar "^2.0.1"
|
||||
cross-zip "^2.1.5"
|
||||
asar "^3.0.0"
|
||||
debug "^4.0.1"
|
||||
electron-notarize "^0.2.0"
|
||||
electron-notarize "^1.0.0"
|
||||
electron-osx-sign "^0.4.11"
|
||||
fs-extra "^8.1.0"
|
||||
extract-zip "^2.0.0"
|
||||
filenamify "^4.1.0"
|
||||
fs-extra "^9.0.0"
|
||||
galactus "^0.2.1"
|
||||
get-package-info "^1.0.0"
|
||||
junk "^3.1.0"
|
||||
@@ -17449,9 +17407,8 @@ electron-packager@14.1.1:
|
||||
plist "^3.0.0"
|
||||
rcedit "^2.0.0"
|
||||
resolve "^1.1.6"
|
||||
sanitize-filename "^1.6.0"
|
||||
semver "^6.0.0"
|
||||
yargs-parser "^16.0.0"
|
||||
semver "^7.1.3"
|
||||
yargs-parser "^19.0.1"
|
||||
|
||||
electron-publish@22.13.1:
|
||||
version "22.13.1"
|
||||
@@ -19009,7 +18966,7 @@ extract-text-webpack-plugin@4.0.0-beta.0:
|
||||
schema-utils "^0.4.5"
|
||||
webpack-sources "^1.1.0"
|
||||
|
||||
extract-zip@2.0.1, extract-zip@^2.0.1:
|
||||
extract-zip@2.0.1, extract-zip@^2.0.0, extract-zip@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
|
||||
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
|
||||
@@ -19334,6 +19291,20 @@ filename-regex@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
|
||||
integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=
|
||||
|
||||
filename-reserved-regex@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
|
||||
integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik=
|
||||
|
||||
filenamify@^4.1.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106"
|
||||
integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==
|
||||
dependencies:
|
||||
filename-reserved-regex "^2.0.0"
|
||||
strip-outer "^1.0.1"
|
||||
trim-repeated "^1.0.0"
|
||||
|
||||
filesize@3.6.1, filesize@^3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
|
||||
@@ -19780,6 +19751,11 @@ follow-redirects@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6"
|
||||
integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==
|
||||
|
||||
follow-redirects@^1.14.0:
|
||||
version "1.14.5"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381"
|
||||
integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==
|
||||
|
||||
for-in@^0.1.3:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
|
||||
@@ -28558,14 +28534,14 @@ normalize-url@^3.0.0, normalize-url@^3.3.0:
|
||||
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
|
||||
|
||||
normalize-url@^4.1.0, normalize-url@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
|
||||
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
|
||||
version "4.5.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
|
||||
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
|
||||
|
||||
normalize-url@^5.0.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-5.3.0.tgz#8959b3cdaa295b61592c1f245dded34b117618dd"
|
||||
integrity sha512-9/nOVLYYe/dO/eJeQUNaGUF4m4Z5E7cb9oNTKabH+bNf19mqj60txTcveQxL0GlcWLXCxkOu2/LwL8oW0idIDA==
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-5.3.1.tgz#c8485c0f5ba2f9c17a6d2907b56117ae5967f882"
|
||||
integrity sha512-K1c7+vaAP+Yh5bOGmA10PGPpp+6h7WZrl7GwqKhUflBc9flU9pzG27DDeB9+iuhZkE3BJZOcgN1P/2sS5pqrWw==
|
||||
|
||||
normalize.css@^8.0.1:
|
||||
version "8.0.1"
|
||||
@@ -34786,7 +34762,7 @@ sane@^4.0.3:
|
||||
minimist "^1.1.1"
|
||||
walker "~1.0.5"
|
||||
|
||||
sanitize-filename@1.6.3, sanitize-filename@^1.6.0, sanitize-filename@^1.6.1, sanitize-filename@^1.6.3:
|
||||
sanitize-filename@1.6.3, sanitize-filename@^1.6.1, sanitize-filename@^1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378"
|
||||
integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==
|
||||
@@ -35150,7 +35126,7 @@ semver@7.3.4:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@7.3.5, semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
|
||||
semver@7.3.5, semver@^7.0.0, semver@^7.1.1, semver@^7.1.2, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
|
||||
version "7.3.5"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
|
||||
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
|
||||
@@ -36438,9 +36414,9 @@ ssri@^5.2.4:
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
ssri@^6.0.0, ssri@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
@@ -36955,6 +36931,13 @@ strip-json-comments@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
|
||||
integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=
|
||||
|
||||
strip-outer@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631"
|
||||
integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==
|
||||
dependencies:
|
||||
escape-string-regexp "^1.0.2"
|
||||
|
||||
strong-log-transformer@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10"
|
||||
@@ -37921,14 +37904,6 @@ title-case@^2.1.0:
|
||||
no-case "^2.2.0"
|
||||
upper-case "^1.0.3"
|
||||
|
||||
tmp-promise@^1.0.5:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-1.1.0.tgz#bb924d239029157b9bc1d506a6aa341f8b13e64c"
|
||||
integrity sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==
|
||||
dependencies:
|
||||
bluebird "^3.5.0"
|
||||
tmp "0.1.0"
|
||||
|
||||
tmp-promise@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7"
|
||||
@@ -37943,13 +37918,6 @@ tmp@0.0.33, tmp@^0.0.33:
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
tmp@0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877"
|
||||
integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==
|
||||
dependencies:
|
||||
rimraf "^2.6.3"
|
||||
|
||||
tmp@^0.2.0, tmp@^0.2.1, tmp@~0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
|
||||
@@ -38150,6 +38118,13 @@ trim-off-newlines@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
|
||||
integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM=
|
||||
|
||||
trim-repeated@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21"
|
||||
integrity sha1-42RqLqTokTEr9+rObPsFOAvAHCE=
|
||||
dependencies:
|
||||
escape-string-regexp "^1.0.2"
|
||||
|
||||
trim-right@^1.0.0, trim-right@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
|
||||
@@ -41645,14 +41620,6 @@ yargs-parser@^15.0.1:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^16.0.0:
|
||||
version "16.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1"
|
||||
integrity sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^18.1.2:
|
||||
version "18.1.3"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||
@@ -41661,6 +41628,11 @@ yargs-parser@^18.1.2:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^19.0.1:
|
||||
version "19.0.4"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-19.0.4.tgz#99183a3a59268b205c6b04177f2a5bfb46e79ba7"
|
||||
integrity sha512-eXeQm7yXRjPFFyf1voPkZgXQZJjYfjgQUmGPbD2TLtZeIYzvacgWX7sQ5a1HsRgVP+pfKAkRZDNtTGev4h9vhw==
|
||||
|
||||
yargs-parser@^20.2.2, yargs-parser@^20.2.3:
|
||||
version "20.2.9"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||
|
||||
Reference in New Issue
Block a user