Merge pull request #24408 from cypress-io/release/12.0.0

feat(breaking): 12.0 Release
This commit is contained in:
Matt Henkes
2022-12-02 17:07:58 -06:00
committed by GitHub
300 changed files with 5848 additions and 12945 deletions
+12 -97
View File
@@ -27,6 +27,7 @@ mainBuildFilters: &mainBuildFilters
branches:
only:
- develop
- /^release\/\d+\.\d+\.\d+$/
- 'feature/run-all-specs'
# usually we don't build Mac app - it takes a long time
@@ -38,17 +39,16 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'feature/run-all-specs', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'feature/run-all-specs', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
# uncomment & add to the branch conditions below to disable the main linux
# flow if we don't want to test it for a certain branch
@@ -63,11 +63,9 @@ windowsWorkflowFilters: &windows-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'astone123/fix-windows-lint', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
executors:
# the Docker image with Cypress dependencies and Chrome browser
cy-doc:
@@ -130,7 +128,7 @@ commands:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "feature/run-all-specs" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
@@ -461,10 +459,6 @@ commands:
description: chrome channel to install
type: string
default: ''
experimentalSessionAndOrigin:
description: experimental flag to apply
type: boolean
default: false
steps:
- restore_cached_workspace
- when:
@@ -488,13 +482,8 @@ commands:
if [[ -v MAIN_RECORD_KEY ]]; then
# internal PR
if <<parameters.experimentalSessionAndOrigin>>; then
CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \
yarn cypress:run-experimentalSessionAndOrigin --record --parallel --group 5x-driver-<<parameters.browser>>-experimentalSessionAndOrigin --browser <<parameters.browser>>
else
CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \
yarn cypress:run --record --parallel --group 5x-driver-<<parameters.browser>> --browser <<parameters.browser>>
fi
CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \
yarn cypress:run --record --parallel --group 5x-driver-<<parameters.browser>> --browser <<parameters.browser>>
else
# external PR
TESTFILES=$(circleci tests glob "cypress/e2e/**/*.cy.*" | circleci tests split --total=$CIRCLE_NODE_TOTAL)
@@ -503,11 +492,7 @@ commands:
if [[ -z "$TESTFILES" ]]; then
echo "Empty list of test files"
fi
if <<parameters.experimentalSessionAndOrigin>>; then
yarn cypress:run-experimentalSessionAndOrigin --browser <<parameters.browser>> --spec $TESTFILES
else
yarn cypress:run --browser <<parameters.browser>> --spec $TESTFILES
fi
yarn cypress:run --browser <<parameters.browser>> --spec $TESTFILES
fi
working_directory: packages/driver
- verify-mocha-results
@@ -1657,58 +1642,12 @@ jobs:
driver-integration-tests-webkit:
<<: *defaults
resource_class: medium+
parallelism: 5
steps:
- run-driver-integration-tests:
browser: webkit
driver-integration-tests-chrome-experimentalSessionAndOrigin:
<<: *defaults
resource_class: medium
parallelism: 5
steps:
- run-driver-integration-tests:
browser: chrome
install-chrome-channel: stable
experimentalSessionAndOrigin: true
driver-integration-tests-chrome-beta-experimentalSessionAndOrigin:
<<: *defaults
resource_class: medium
parallelism: 5
steps:
- run-driver-integration-tests:
browser: chrome:beta
install-chrome-channel: beta
experimentalSessionAndOrigin: true
driver-integration-tests-firefox-experimentalSessionAndOrigin:
<<: *defaults
resource_class: medium
parallelism: 5
steps:
- run-driver-integration-tests:
browser: firefox
experimentalSessionAndOrigin: true
driver-integration-tests-electron-experimentalSessionAndOrigin:
<<: *defaults
resource_class: medium
parallelism: 5
steps:
- run-driver-integration-tests:
browser: electron
experimentalSessionAndOrigin: true
driver-integration-tests-webkit-experimentalSessionAndOrigin:
<<: *defaults
resource_class: medium
parallelism: 5
steps:
- run-driver-integration-tests:
browser: webkit
experimentalSessionAndOrigin: true
run-reporter-component-tests-chrome:
<<: *defaults
parameters:
@@ -2482,26 +2421,6 @@ linux-x64-workflow: &linux-x64-workflow
context: test-runner:cypress-record-key
requires:
- build
- driver-integration-tests-chrome-experimentalSessionAndOrigin:
context: test-runner:cypress-record-key
requires:
- build
- driver-integration-tests-chrome-beta-experimentalSessionAndOrigin:
context: test-runner:cypress-record-key
requires:
- build
- driver-integration-tests-firefox-experimentalSessionAndOrigin:
context: test-runner:cypress-record-key
requires:
- build
- driver-integration-tests-electron-experimentalSessionAndOrigin:
context: test-runner:cypress-record-key
requires:
- build
- driver-integration-tests-webkit-experimentalSessionAndOrigin:
context: test-runner:cypress-record-key
requires:
- build
- run-frontend-shared-component-tests-chrome:
context: [test-runner:cypress-record-key, test-runner:launchpad-tests, test-runner:percy]
percy: true
@@ -2606,10 +2525,6 @@ linux-x64-workflow: &linux-x64-workflow
- driver-integration-tests-chrome
- driver-integration-tests-chrome-beta
- driver-integration-tests-electron
- driver-integration-tests-firefox-experimentalSessionAndOrigin
- driver-integration-tests-chrome-experimentalSessionAndOrigin
- driver-integration-tests-chrome-beta-experimentalSessionAndOrigin
- driver-integration-tests-electron-experimentalSessionAndOrigin
- system-tests-non-root
- system-tests-firefox
- system-tests-electron
+1 -1
View File
@@ -116,7 +116,7 @@
"cypress": "bin/cypress"
},
"engines": {
"node": ">=12.0.0"
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
},
"types": "types",
"exports": {
+1 -1
View File
@@ -173,7 +173,7 @@ declare namespace CypressCommandLine {
title: string[]
state: string
body: string
/**
/**
* Error string as it's presented in console if the test fails
*/
displayError: string | null
+162 -174
View File
@@ -50,6 +50,9 @@ declare namespace Cypress {
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 QueryFn<T extends keyof ChainableMethods> {
(this: Command, ...args: Parameters<ChainableMethods[T]>): (subject: any) => any
}
interface ObjectLike {
[key: string]: any
}
@@ -128,6 +131,58 @@ declare namespace Cypress {
unsupportedVersion?: boolean
}
interface Ensure {
/**
* Throws an error if `subject` is not one of the passed in `type`s.
*/
isType(subject: any, type: PrevSubject[], commandName: string, cy: Chainable): void
/**
* Throws an error if `subject` is not a DOM element.
*/
isElement(subject: any, commandName: string, cy: Chainable): void
/**
* Throws an error if `subject` is not a `document`.
*/
isDocument(subject: any, commandName: string, cy: Chainable): void
/**
* Throws an error if `subject` is not a `window`.
*/
isWindow(subject: any, commandName: string, cy: Chainable): void
/**
* Throws an error if `subject` is not a DOM element attached to the application under test.
*/
isAttached(subject: any, commandName: string, cy: Chainable, onFail?: Log): void
/**
* Throws an error if `subject` is a disabled DOM element.
*/
isNotDisabled(subject: any, commandName: string, onFail?: Log): void
/**
* Throws an error if `subject` is a DOM element hidden by any of its parent elements.
*/
isNotHiddenByAncestors(subject: any, commandName: string, onFail?: Log): void
/**
* Throws an error if `subject` is a read-only form element.
*/
isNotReadonly(subject: any, commandName: string, onFail?: Log): void
/**
* Throws an error if `subject` is a read-only form element.
*/
isScrollable(subject: any, commandName: string, onFail?: Log): void
/**
* Throws an error if `subject` is not a DOM element visible in the AUT.
*/
isVisible(subject: any, commandName: string, onFail?: Log): void
}
interface LocalStorage {
/**
* Called internally to clear `localStorage` in two situations.
@@ -305,6 +360,12 @@ declare namespace Cypress {
*/
sinon: sinon.SinonStatic
/**
* Utility functions for ensuring various properties about a subject.
* @see https://on.cypress.io/custom-queries
*/
ensure: Ensure
/**
* Cypress version string. i.e. "1.1.2"
* @see https://on.cypress.io/version
@@ -496,30 +557,92 @@ declare namespace Cypress {
*/
log(options: Partial<LogConfig>): Log
/**
* @see https://on.cypress.io/api/commands
*/
Commands: {
/**
* Add a custom command
* @see https://on.cypress.io/api/commands
*/
add<T extends keyof Chainable>(name: T, fn: CommandFn<T>): void
/**
* Add a custom parent command
* @see https://on.cypress.io/api/commands#Parent-Commands
*/
add<T extends keyof Chainable>(name: T, options: CommandOptions & {prevSubject: false}, fn: CommandFn<T>): void
/**
* Add a custom child command
* @see https://on.cypress.io/api/commands#Child-Commands
*/
add<T extends keyof Chainable, S = any>(name: T, options: CommandOptions & {prevSubject: true}, fn: CommandFnWithSubject<T, S>): void
/**
* Add a custom child or dual command
* @see https://on.cypress.io/api/commands#Validations
*/
add<T extends keyof Chainable, S extends PrevSubject>(
name: T, options: CommandOptions & { prevSubject: S | ['optional'] }, fn: CommandFnWithSubject<T, PrevSubjectMap[S]>,
): void
/**
* Add a custom command that allows multiple types as the prevSubject
* @see https://on.cypress.io/api/commands#Validations#Allow-Multiple-Types
*/
add<T extends keyof Chainable, S extends PrevSubject>(
name: T, options: CommandOptions & { prevSubject: S[] }, fn: CommandFnWithSubject<T, PrevSubjectMap<void>[S]>,
): void
/**
* Add one or more custom commands
* @see https://on.cypress.io/api/commands
*/
addAll<T extends keyof Chainable>(fns: CommandFns): void
/**
* Add one or more custom parent commands
* @see https://on.cypress.io/api/commands#Parent-Commands
*/
addAll<T extends keyof Chainable>(options: CommandOptions & {prevSubject: false}, fns: CommandFns): void
/**
* Add one or more custom child commands
* @see https://on.cypress.io/api/commands#Child-Commands
*/
addAll<T extends keyof Chainable, S = any>(options: CommandOptions & { prevSubject: true }, fns: CommandFnsWithSubject<S>): void
/**
* Add one or more custom commands that validate their prevSubject
* @see https://on.cypress.io/api/commands#Validations
*/
addAll<T extends keyof Chainable, S extends PrevSubject>(
options: CommandOptions & { prevSubject: S | ['optional'] }, fns: CommandFnsWithSubject<PrevSubjectMap[S]>,
): void
/**
* Add one or more custom commands that allow multiple types as their prevSubject
* @see https://on.cypress.io/api/commands#Allow-Multiple-Types
*/
addAll<T extends keyof Chainable, S extends PrevSubject>(
options: CommandOptions & { prevSubject: S[] }, fns: CommandFnsWithSubject<PrevSubjectMap<void>[S]>,
): void
/**
* Overwrite an existing Cypress command with a new implementation
* @see https://on.cypress.io/api/commands#Overwrite-Existing-Commands
*/
overwrite<T extends keyof Chainable>(name: T, fn: CommandFnWithOriginalFn<T>): void
/**
* Overwrite an existing Cypress command with a new implementation
* @see https://on.cypress.io/api/commands#Overwrite-Existing-Commands
*/
overwrite<T extends keyof Chainable, S extends PrevSubject>(name: T, fn: CommandFnWithOriginalFnAndSubject<T, PrevSubjectMap[S]>): void
/**
* Add a custom query
* @see https://on.cypress.io/api/custom-queries
*/
addQuery<T extends keyof Chainable>(name: T, fn: QueryFn<T>): void
}
/**
@@ -527,16 +650,6 @@ declare namespace Cypress {
*/
Cookies: {
debug(enabled: boolean, options?: Partial<DebugOptions>): void
/**
* @deprecated Use `cy.session()` instead.
* @see https://on.cypress.io/session
*/
preserveOnce(...names: string[]): void
/**
* @deprecated Use `cy.session()` instead.
* @see https://on.cypress.io/session
*/
defaults(options: Partial<CookieDefaults>): CookieDefaults
}
/**
@@ -635,13 +748,6 @@ declare namespace Cypress {
defaults(options: Partial<KeyboardDefaultsOptions>): void
}
/**
* @see https://on.cypress.io/api/api-server
*/
Server: {
defaults(options: Partial<ServerOptions>): void
}
/**
* @see https://on.cypress.io/screenshot-api
*/
@@ -809,7 +915,7 @@ declare namespace Cypress {
clear(options?: Partial<ClearOptions>): Chainable<Subject>
/**
* Clear a specific browser cookie for the current superdomain or for the domain specified.
* Clear a specific browser cookie for the current hostname or for the domain specified.
* Cypress automatically clears all cookies before each test to prevent state from being shared across tests. You shouldn't need to use this command unless you're using it to clear a specific cookie inside a single test.
*
* @see https://on.cypress.io/clearcookie
@@ -817,7 +923,7 @@ declare namespace Cypress {
clearCookie(name: string, options?: CookieOptions): Chainable<null>
/**
* Clear all browser cookies for the current superdomain or for the domain specified.
* Clear all browser cookies for the current hostname or for the domain specified.
* Cypress automatically clears all cookies before each test to prevent state from being shared across tests. You shouldn't need to use this command unless you're using it to clear all cookies or specific cookies inside a single test.
*
* @see https://on.cypress.io/clearcookies
@@ -1147,8 +1253,6 @@ declare namespace Cypress {
/**
* Save/Restore browser Cookies, LocalStorage, and SessionStorage data resulting from the supplied `setup` function.
*
* Only available if the `experimentalSessionAndOrigin` config option is enabled.
*
* @see https://on.cypress.io/session
*/
session(id: string | object, setup: () => void, options?: SessionOptions): Chainable<null>
@@ -1310,14 +1414,14 @@ declare namespace Cypress {
get<S = any>(alias: string, options?: Partial<Loggable & Timeoutable & Withinable & Shadow>): Chainable<S>
/**
* Get a browser cookie by its name for the current superdomain or for the domain specified.
* Get a browser cookie by its name for the current hostname or for the domain specified.
*
* @see https://on.cypress.io/getcookie
*/
getCookie(name: string, options?: CookieOptions): Chainable<Cookie | null>
/**
* Get all of the browser cookies for the current superdomain or for the domain specified.
* Get all of the browser cookies for the current hostname or for the domain specified.
*
* @see https://on.cypress.io/getcookies
*/
@@ -1491,6 +1595,13 @@ declare namespace Cypress {
*/
not(selector: string, options?: Partial<Loggable & Timeoutable>): Chainable<JQuery>
/**
* Invoke a command synchronously, without using the command queue.
*
* @see https://on.cypress.io/custom-queries
*/
now(name: string, ...args: any[]): Promise<any> | ((subject: any) => any)
/**
* These events come from Cypress as it issues commands and reacts to their state. These are all useful to listen to for debugging purposes.
* @see https://on.cypress.io/catalog-of-events#App-Events
@@ -1739,66 +1850,6 @@ declare namespace Cypress {
*/
root<E extends Node = HTMLHtmlElement>(options?: Partial<Loggable>): Chainable<JQuery<E>> // can't do better typing unless we ignore the `.within()` case
/**
* @deprecated Use `cy.intercept()` instead.
*
* Use `cy.route()` to manage the behavior of network requests.
* @see https://on.cypress.io/route
* @example
* cy.server()
* cy.route('https://localhost:7777/users', [{id: 1, name: 'Pat'}])
*/
route(url: string | RegExp, response?: string | object): Chainable<null>
/**
* @deprecated Use `cy.intercept()` instead.
*
* Spy or stub request with specific method and url.
*
* @see https://on.cypress.io/route
* @example
* cy.server()
* // spy on POST /todos requests
* cy.route('POST', '/todos').as('add-todo')
*/
route(method: string, url: string | RegExp, response?: string | object): Chainable<null>
/**
* @deprecated Use `cy.intercept()` instead.
*
* Set a route by returning an object literal from a callback function.
* Functions that return a Promise will automatically be awaited.
*
* @see https://on.cypress.io/route
* @example
* cy.server()
* cy.route(() => {
* // your logic here
* // return an appropriate routing object here
* return {
* method: 'POST',
* url: '/comments',
* response: this.commentsFixture
* }
* })
*/
route(fn: () => RouteOptions): Chainable<null>
/**
* @deprecated Use `cy.intercept()` instead.
*
* Spy or stub a given route.
*
* @see https://on.cypress.io/route
* @example
* cy.server()
* cy.route({
* method: 'DELETE',
* url: '/users',
* status: 412,
* delay: 1000
* // and other options, see documentation
* })
*/
route(options: Partial<RouteOptions>): Chainable<null>
/**
* Take a screenshot of the application under test and the Cypress Command Log.
*
@@ -1844,26 +1895,6 @@ declare namespace Cypress {
*/
select(valueOrTextOrIndex: string | number | Array<string | number>, options?: Partial<SelectOptions>): Chainable<Subject>
/**
* @deprecated Use `cy.intercept()` instead.
*
* Start a server to begin routing responses to `cy.route()` and `cy.request()`.
*
* @example
* // start server
* cy.server()
* // get default server options
* cy.server().should((server) => {
* expect(server.delay).to.eq(0)
* expect(server.method).to.eq('GET')
* expect(server.status).to.eq(200)
* // and many others options
* })
*
* @see https://on.cypress.io/server
*/
server(options?: Partial<ServerOptions>): Chainable<ServerOptions>
/**
* Set a browser cookie.
*
@@ -2465,10 +2496,6 @@ declare namespace Cypress {
type Agent<T extends sinon.SinonSpy> = SinonSpyAgent<T> & T
interface CookieDefaults {
preserve: string | string[] | RegExp | ((cookie: Cookie) => boolean)
}
interface Failable {
/**
* Whether to fail on response codes other than 2xx and 3xx
@@ -2698,7 +2725,7 @@ declare namespace Cypress {
interface CookieOptions extends Partial<Loggable & Timeoutable> {
/**
* Domain to set cookies on or get cookies from
* @default superdomain of the current app under test
* @default hostname of the current app under test
*/
domain?: string
}
@@ -2878,8 +2905,7 @@ declare namespace Cypress {
*/
supportFile: string | false
/**
* The test isolation ensures a clean browser context between tests. This option is only available when
* `experimentalSessionAndOrigin=true`.
* The test isolation ensures a clean browser context between tests.
*
* Cypress will always reset/clear aliases, intercepts, clock, and viewport before each test
* to ensure a clean test slate; i.e. this configuration only impacts the browser context.
@@ -2887,23 +2913,23 @@ declare namespace Cypress {
* Note: the [`cy.session()`](https://on.cypress.io/session) command will inherent this value to determine whether
* or not the page is cleared when the command executes. This command is only available in end-to-end testing.
*
* - on - The page is cleared before each test. Cookies, local storage and session storage in all domains are cleared
* - true - The page is cleared before each test. Cookies, local storage and session storage in all domains are cleared
* before each test. The `cy.session()` command will also clear the page and current browser context when creating
* or restoring the browser session.
* - off - The current browser state will persist between tests. The page does not clear before the test and cookies, local
* - false - The current browser state will persist between tests. The page does not clear before the test and cookies, local
* storage and session storage will be available in the next test. The `cy.session()` command will only clear the
* current browser context when creating or restoring the browser session - the current page will not clear.
*
* Tradeoffs:
* Turning test isolation off may improve performance of end-to-end tests, however, previous tests could impact the
* browser state of the next test and cause inconsistency when using .only(). Be mindful to write isolated tests when
* test isolation is off. If a test in the suite impacts the state of other tests and it were to fail, you could see
* test isolation is false. If a test in the suite impacts the state of other tests and it were to fail, you could see
* misleading errors in later tests which makes debugging clunky. See the [documentation](https://on.cypress.io/test-isolation)
* for more information.
*
* @default null, when experimentalSessionAndOrigin=false. The default is 'on' when experimentalSessionAndOrigin=true.
* @default true
*/
testIsolation: null | 'on' | 'off'
testIsolation: boolean
/**
* Path to folder where videos will be saved after a headless or CI run
* @default "cypress/videos"
@@ -2964,11 +2990,6 @@ declare namespace Cypress {
* @default false
*/
experimentalInteractiveRunEvents: boolean
/**
* Enables cross-origin and improved session support, including the `cy.origin` and `cy.session` commands. See https://on.cypress.io/origin and https://on.cypress.io/session.
* @default false
*/
experimentalSessionAndOrigin: boolean
/**
* Whether Cypress will search for and replace obstructive code in third party .js or .html files.
* NOTE: Setting this flag to true removes Subresource Integrity (SRI).
@@ -3065,6 +3086,11 @@ declare namespace Cypress {
* @default false
*/
experimentalRunAllSpecs?: boolean
/**
* Enables support for require/import within cy.origin.
* @default false
*/
experimentalOriginDependencies?: boolean
}
/**
@@ -3140,19 +3166,17 @@ declare namespace Cypress {
socketIoRoute: string
spec: Cypress['spec'] | null
specs: Array<Cypress['spec']>
xhrRoute: string
xhrUrl: string
}
interface SuiteConfigOverrides extends Partial<
Pick<ConfigOptions, 'animationDistanceThreshold' | 'blockHosts' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'numTestsKeptInMemory' | 'pageLoadTimeout' | 'redirectionLimit' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'screenshotOnRunFailure' | 'slowTestThreshold' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations' | 'experimentalSessionAndOrigin'>
Pick<ConfigOptions, 'animationDistanceThreshold' | 'blockHosts' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'numTestsKeptInMemory' | 'pageLoadTimeout' | 'redirectionLimit' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'screenshotOnRunFailure' | 'slowTestThreshold' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>
>, Partial<Pick<ResolvedConfigOptions, 'baseUrl' | 'testIsolation'>> {
browser?: IsBrowserMatcher | IsBrowserMatcher[]
keystrokeDelay?: number
}
interface TestConfigOverrides extends Partial<
Pick<ConfigOptions, 'animationDistanceThreshold' | 'blockHosts' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'numTestsKeptInMemory' | 'pageLoadTimeout' | 'redirectionLimit' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'screenshotOnRunFailure' | 'slowTestThreshold' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations' | 'experimentalSessionAndOrigin'>
Pick<ConfigOptions, 'animationDistanceThreshold' | 'blockHosts' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'numTestsKeptInMemory' | 'pageLoadTimeout' | 'redirectionLimit' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'screenshotOnRunFailure' | 'slowTestThreshold' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>
>, Partial<Pick<ResolvedConfigOptions, 'baseUrl'>> {
browser?: IsBrowserMatcher | IsBrowserMatcher[]
keystrokeDelay?: number
@@ -3209,7 +3233,7 @@ declare namespace Cypress {
}
}
interface ComponentConfigOptions<ComponentDevServerOpts = any> extends Omit<CoreConfigOptions, 'baseUrl' | 'experimentalSessionAndOrigin' | 'experimentalStudio'> {
interface ComponentConfigOptions<ComponentDevServerOpts = any> extends Omit<CoreConfigOptions, 'baseUrl' | 'experimentalStudio'> {
devServer: DevServerFn<ComponentDevServerOpts> | DevServerConfigOptions
devServerConfig?: ComponentDevServerOpts
/**
@@ -3398,28 +3422,6 @@ declare namespace Cypress {
interval: number
}
/**
* Setting default options for cy.server()
* @see https://on.cypress.io/server
*/
interface ServerOptions {
delay: number
method: HttpMethod
status: number
headers: object
response: any
onRequest(...args: any[]): void
onResponse(...args: any[]): void
onAbort(...args: any[]): void
enable: boolean
force404: boolean
urlMatchingOptions: object
ignore(xhr: Request): void
onAnyRequest(route: RouteOptions, proxy: any): void
onAnyResponse(route: RouteOptions, proxy: any): void
onAnyAbort(route: RouteOptions, proxy: any): void
}
interface Session {
// Clear all saved sessions and re-run the current spec file.
clearAllSavedSessions: () => Promise<void>
@@ -5839,7 +5841,7 @@ declare namespace Cypress {
* Useful for debugging purposes if you're confused about the order in which commands will execute.
* @see https://on.cypress.io/catalog-of-events#App-Events
*/
(action: 'command:enqueued', fn: (command: EnqueuedCommand) => void): Cypress
(action: 'command:enqueued', fn: (command: EnqueuedCommandAttributes) => void): Cypress
/**
* Fires when cy begins actually running and executing your command.
* Useful for debugging and understanding how the command queue is async.
@@ -5967,7 +5969,7 @@ declare namespace Cypress {
sameSite?: SameSiteStatus
}
interface EnqueuedCommand {
interface EnqueuedCommandAttributes {
id: string
name: string
args: any[]
@@ -5975,9 +5977,17 @@ declare namespace Cypress {
chainerId: string
injected: boolean
userInvocationStack?: string
query?: boolean
fn(...args: any[]): any
}
interface Command {
get<K extends keyof EnqueuedCommandAttributes>(attr: K): EnqueuedCommandAttributes[K]
get(): EnqueuedCommandAttributes
set<K extends keyof EnqueuedCommandAttributes>(key: K, value: EnqueuedCommandAttributes[K]): Log
set(options: Partial<EnqueuedCommandAttributes>): Log
}
interface Exec {
code: number
stdout: string
@@ -6056,28 +6066,6 @@ declare namespace Cypress {
viewportHeight: number
}
interface WaitXHR {
duration: number
id: string
method: HttpMethod
request: {
body: string | ObjectLike
headers: ObjectLike
}
requestBody: WaitXHR['request']['body']
requestHeaders: WaitXHR['request']['headers']
response: {
body: string | ObjectLike
headers: ObjectLike
}
responseBody: WaitXHR['response']['body']
responseHeaders: WaitXHR['response']['headers']
status: number
statusMessage: string
url: string
xhr: XMLHttpRequest
}
type Encodings = 'ascii' | 'base64' | 'binary' | 'hex' | 'latin1' | 'utf8' | 'utf-8' | 'ucs2' | 'ucs-2' | 'utf16le' | 'utf-16le' | null
type PositionType = 'topLeft' | 'top' | 'topRight' | 'left' | 'center' | 'right' | 'bottomLeft' | 'bottom' | 'bottomRight'
type ViewportPreset = 'macbook-16' | 'macbook-15' | 'macbook-13' | 'macbook-11' | 'ipad-2' | 'ipad-mini' | 'iphone-xr' | 'iphone-x' | 'iphone-6+' | 'iphone-se2' | 'iphone-8' | 'iphone-7' | 'iphone-6' | 'iphone-5' | 'iphone-4' | 'iphone-3' | 'samsung-s10' | 'samsung-note9'
+1 -1
View File
@@ -46,7 +46,7 @@ Cypress.on('scrolled', ($el) => {
})
Cypress.on('command:enqueued', (command) => {
command // $ExpectType EnqueuedCommand
command // $ExpectType EnqueuedCommandAttributes
})
Cypress.on('command:start', (command) => {
+25 -3
View File
@@ -39,7 +39,7 @@ namespace CypressConfigTests {
Cypress.config({ e2e: { baseUrl: '.', }}) // $ExpectError
Cypress.config({ component: { baseUrl: '.', devServer: () => ({} as any) } }) // $ExpectError
Cypress.config({ e2e: { indexHtmlFile: 'index.html' } }) // $ExpectError
Cypress.config({ testIsolation: 'off' }) // $ExpectError
Cypress.config({ testIsolation: false }) // $ExpectError
Cypress.config('taskTimeout') // $ExpectType number
Cypress.config('includeShadowDom') // $ExpectType boolean
@@ -69,6 +69,7 @@ namespace CypressIsCyTests {
declare namespace Cypress {
interface Chainable {
newCommand: (arg: string) => Chainable<number>
newQuery: (arg: string) => Chainable<number>
}
}
@@ -291,6 +292,27 @@ namespace CypressCommandsTests {
return originalFn(element, text, options)
})
Cypress.Commands.addQuery('newQuery', function(arg) {
this // $ExpectType Command
arg // $ExpectType string
return () => 3
})
}
namespace CypressNowTest {
cy.now('get') // $ExpectType Promise<any> | ((subject: any) => any)
}
namespace CypressEnsuresTest {
Cypress.ensure.isType('', ['optional', 'element'], 'newQuery', cy) // $ExpectType void
Cypress.ensure.isElement('', 'newQuery', cy) // $ExpectType void
Cypress.ensure.isWindow('', 'newQuery', cy) // $ExpectType void
Cypress.ensure.isDocument('', 'newQuery', cy) // $ExpectType void
Cypress.ensure.isAttached('', 'newQuery', cy) // $ExpectType void
Cypress.ensure.isNotDisabled('', 'newQuery') // $ExpectType void
Cypress.ensure.isVisible('', 'newQuery') // $ExpectType void
}
namespace CypressLogsTest {
@@ -868,7 +890,7 @@ namespace CypressTestConfigOverridesTests {
retries: { run: 3 } // $ExpectError
}, () => { })
it('test', {
testIsolation: 'off', // $ExpectError
testIsolation: false, // $ExpectError
}, () => { })
it.skip('test', {}, () => {})
@@ -887,7 +909,7 @@ namespace CypressTestConfigOverridesTests {
}, () => {})
describe('suite', {
testIsolation: 'off',
testIsolation: false,
}, () => {})
context('suite', {}, () => {})
-15
View File
@@ -18,14 +18,6 @@ result // $ExpectType boolean
Cypress.minimatch('/users/1/comments', '/users/*/comments') // $ExpectType boolean
// check if cy.server() yields default server options
cy.server().should((server) => {
server // $ExpectType ServerOptions
expect(server.delay).to.eq(0)
expect(server.method).to.eq('GET')
expect(server.status).to.eq(200)
})
cy.visit('https://www.acme.com/', {
auth: {
username: 'wile',
@@ -33,13 +25,6 @@ cy.visit('https://www.acme.com/', {
}
})
const serverOptions: Partial<Cypress.ServerOptions> = {
delay: 100,
ignore: () => true
}
cy.server(serverOptions)
Cypress.spec.name // $ExpectType string
Cypress.spec.relative // $ExpectType string
Cypress.spec.absolute // $ExpectType string
+1 -1
View File
@@ -27,7 +27,7 @@ const cypressSchematicPackagePath = path.join(__dirname, '..')
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-13', 'angular-14']
describe('ng add @cypress/schematic / only e2e', function () {
this.timeout(1000 * 60 * 4)
this.timeout(1000 * 60 * 5)
for (const project of ANGULAR_PROJECTS) {
it('should install e2e files by default', async () => {
+12
View File
@@ -44,6 +44,18 @@ export function setupHooks (optionalCallback?: Function) {
)
})
Cypress.Commands.overwrite('session', () => {
throw new Error(
'cy.session from a component spec is not allowed',
)
})
Cypress.Commands.overwrite('origin', () => {
throw new Error(
'cy.origin from a component spec is not allowed',
)
})
// @ts-ignore
Cypress.on('test:before:run', () => {
optionalCallback?.()
@@ -7,8 +7,7 @@ You can install and bring [Testing library/Cypress](https://testing-library.com/
```js
it('loads and displays greeting (testing-lib)', () => {
cy.server()
cy.route('/greeting', { greeting: 'Hello there' }).as('greet')
cy.intercept('/greeting', { greeting: 'Hello there' }).as('greet')
const url = '/greeting'
mount(<Fetch url={url} />)
@@ -19,7 +18,7 @@ it('loads and displays greeting (testing-lib)', () => {
cy.findByRole('heading').should('have.text', 'Hello there')
cy.findByRole('button').should('be.disabled')
cy.get('@greet')
.its('url')
.its('response.url')
.should('match', /\/greeting$/)
})
```
@@ -3,8 +3,7 @@ import { mount } from '@cypress/react'
import Fetcher from './fetcher'
it('loads and displays greeting', () => {
cy.server()
cy.route('/greeting', { greeting: 'Hello there' }).as('greet')
cy.intercept('/greeting', { greeting: 'Hello there' }).as('greet')
const url = '/greeting'
@@ -14,6 +13,6 @@ it('loads and displays greeting', () => {
cy.get('[role=heading]').should('have.text', 'Hello there')
cy.get('[role=button]').should('be.disabled')
cy.get('@greet')
.its('url')
.its('response.url')
.should('match', /\/greeting$/)
})
@@ -7,8 +7,7 @@ import Fetcher from './fetcher'
// NOTE: this doesn't work because of update to the @testing/library v7. Looks like build issue with current webpack config
it.skip('loads and displays greeting (testing-lib)', () => {
cy.server()
cy.route('/greeting', { greeting: 'Hello there' }).as('greet')
cy.intercept('/greeting', { greeting: 'Hello there' }).as('greet')
const url = '/greeting'
@@ -21,6 +20,6 @@ it.skip('loads and displays greeting (testing-lib)', () => {
cy.findByRole('heading').should('have.text', 'Hello there')
cy.findByRole('button').should('be.disabled')
cy.get('@greet')
.its('url')
.its('response.url')
.should('match', /\/greeting$/)
})
@@ -15,14 +15,8 @@ context('Users', () => {
})
describe('Network State', () => {
beforeEach(() => {
cy.server()
// mount the component after defining routes in tests
// preventing race conditions where you wait on untouched routes
})
it('can inspect real data in XHR', () => {
cy.route('/users?_limit=3').as('users')
cy.intercept('/users?_limit=3').as('users')
mount(<Users />)
cy.wait('@users')
.its('response.body')
@@ -34,7 +28,7 @@ context('Users', () => {
it('can display mock XHR response', () => {
const users = [{ id: 1, name: 'foo' }]
cy.route('GET', '/users?_limit=3', users).as('users')
cy.intercept('GET', '/users?_limit=3', users).as('users')
mount(<Users />)
cy.get('li')
.should('have.length', 1)
@@ -45,7 +39,7 @@ context('Users', () => {
it('can inspect mocked XHR', () => {
const users = [{ id: 1, name: 'foo' }]
cy.route('GET', '/users?_limit=3', users).as('users')
cy.intercept('GET', '/users?_limit=3', users).as('users')
mount(<Users />)
cy.wait('@users')
.its('response.body')
@@ -55,10 +49,8 @@ context('Users', () => {
it('can delay and wait on XHR', () => {
const users = [{ id: 1, name: 'foo' }]
cy.route({
method: 'GET',
url: '/users?_limit=3',
response: users,
cy.intercept('GET', '/users?_limit=3', {
body: users,
delay: 1000,
}).as('users')
@@ -13,15 +13,9 @@ describe('Users with Fetch', () => {
// https://github.com/bahmutov/@cypress/react/issues/347
context('mocking', () => {
beforeEach(() => {
cy.server()
// mount the component after defining routes in tests
// preventing race conditions where you wait on untouched routes
})
it('can inspect real data from the server', () => {
// spy on the request
cy.route('/users?_limit=3').as('users')
cy.intercept('/users?_limit=3').as('users')
mount(<Users />)
cy.wait('@users')
.its('response.body')
@@ -34,7 +28,7 @@ describe('Users with Fetch', () => {
const users = [{ id: 1, name: 'foo' }]
// stub the request
cy.route('GET', '/users?_limit=3', users).as('users')
cy.intercept('GET', '/users?_limit=3', users).as('users')
mount(<Users />)
cy.get('li')
.should('have.length', 1)
@@ -45,7 +39,7 @@ describe('Users with Fetch', () => {
it('can inspect mocked network reaponse', () => {
const users = [{ id: 1, name: 'foo' }]
cy.route('GET', '/users?_limit=3', users).as('users')
cy.intercept('GET', '/users?_limit=3', users).as('users')
mount(<Users />)
cy.wait('@users')
.its('response.body')
@@ -55,10 +49,8 @@ describe('Users with Fetch', () => {
it('can delay and wait on Ajax call', () => {
const users = [{ id: 1, name: 'foo' }]
cy.route({
method: 'GET',
url: '/users?_limit=3',
response: users,
cy.intercept('GET', '/users?_limit=3', {
body: users,
delay: 1000,
}).as('users')
-1
View File
@@ -3,7 +3,6 @@ import { defineConfig } from 'cypress'
export default defineConfig({
projectId: 'ypt4pf',
e2e: {
experimentalSessionAndOrigin: true,
defaultCommandTimeout: 10000, // these take a bit longer b/c they're e2e open mode test
async setupNodeEvents (on, config) {
if (!process.env.HTTP_PROXY_TARGET_FOR_ORIGIN_REQUESTS) {
@@ -10,21 +10,18 @@ describe('Fetching users with polyfill', () => {
})
it('can spy on the fetch requests', () => {
cy.server()
cy.route('/users?_limit=3').as('users')
cy.intercept('/users?_limit=3').as('users')
mount(Users)
cy.wait('@users')
.its('responseBody.length')
.its('response.body.length')
.then((length) => {
cy.get('.user').should('have.length', length)
})
})
it('shows loading UI while fetch is happening', () => {
cy.server()
cy.route({
url: '/users?_limit=3',
response: 'fixture:users',
cy.intercept('/users?_limit=3', {
fixture: 'users',
delay: 1000,
})
+1 -59
View File
@@ -9,7 +9,7 @@ describe('AjaxList', () => {
context('using cy.intercept()', () => {
// because this component loads data right away
// we need to setup XHR intercepts BEFORE mounting it
// thus each test will first do its "cy.route"
// thus each test will first do its "cy.intercept"
// then will mount the component
it('loads list of posts', () => {
@@ -60,62 +60,4 @@ describe('AjaxList', () => {
cy.get('li').should('have.length', 1)
})
})
context('using cy.route()', () => {
// because this component loads data right away
// we need to setup XHR intercepts BEFORE mounting it
// thus each test will first do its "cy.route"
// then will mount the component
it('loads list of posts', () => {
mount(AjaxList)
cy.get('li').should('have.length', 3)
})
it('can inspect real data in XHR', () => {
cy.server()
cy.route('/users?_limit=3').as('users')
mount(AjaxList)
cy.wait('@users').its('response.body').should('have.length', 3)
})
it('can display mock XHR response', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
cy.route('GET', '/users?_limit=3', users).as('users')
mount(AjaxList)
cy.get('li').should('have.length', 1).first().contains('foo')
})
it('can inspect mocked XHR', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
cy.route('GET', '/users?_limit=3', users).as('users')
mount(AjaxList)
cy.wait('@users').its('response.body').should('deep.equal', users)
})
it('can delay and wait on XHR', () => {
cy.server()
const users = [{ id: 1, name: 'foo' }]
cy.route({
method: 'GET',
url: '/users?_limit=3',
response: users,
delay: 1000,
}).as('users')
mount(AjaxList)
cy.get('li').should('have.length', 0)
cy.wait('@users')
cy.get('li').should('have.length', 1)
})
})
})
@@ -1 +1 @@
**/tsconfig.json
**/tsconfig.json
+2 -1
View File
@@ -222,7 +222,7 @@ const preprocessor: WebpackPreprocessor = (options: PreprocessorOptions = {}): F
})
.tap((opts) => {
if (opts.devtool === false) {
// disable any overrides if we've explictly turned off sourcemaps
// disable any overrides if we've explicitly turned off sourcemaps
overrideSourceMaps(false, options.typescript)
return
@@ -248,6 +248,7 @@ const preprocessor: WebpackPreprocessor = (options: PreprocessorOptions = {}): F
// so that it's working with plain javascript
webpackOptions.module.rules.unshift({
test: /\.(js|ts|jsx|tsx)$/,
exclude: /node_modules/,
use: [{
loader: require.resolve('@cypress/webpack-preprocessor/dist/lib/cross-origin-callback-loader.js'),
options: {
+5
View File
@@ -0,0 +1,5 @@
# Unreleased Cypress App Changes
## Breaking
- Removed the `rawJson` configuration data from `Cypress.state()`. Addressed [#23945](https://github.com/cypress-io/cypress/issues/23945).
@@ -16,7 +16,8 @@ describe('Reporter Header', () => {
cy.get('[data-selected-spec="false"]').should('have.length', '27')
})
it('filters the list of specs when searching for specs', () => {
// TODO: Reenable as part of https://github.com/cypress-io/cypress/issues/23902
it.skip('filters the list of specs when searching for specs', () => {
cy.get('body').type('f')
cy.findByTestId('specs-list-panel').within(() => {
@@ -28,7 +29,7 @@ describe('Reporter Header', () => {
cy.get('@searchInput').clear()
cy.get('[data-cy="spec-file-item"]').should('have.length', 3)
cy.get('[data-cy="spec-file-item"]').should('have.length', 23)
cy.get('@searchInput').type('asdf', { force: true })
@@ -321,107 +321,6 @@ describe('errors ui', {
})
})
it('cy.route', () => {
const verify = loadErrorSpec({
filePath: 'errors/route.cy.js',
failCount: 9,
})
verify('callback assertion failure', {
column: 27,
message: `expected 'actual' to equal 'expected'`,
})
verify('callback exception', {
column: 12,
message: 'bar is not a function',
})
verify('command failure', {
column: 10,
message: 'Expected to find element: #does-not-exist, but never found it',
})
verify('onAbort assertion failure', {
column: 29,
codeFrameText: 'onAbort',
message: `expected 'actual' to equal 'expected'`,
})
verify('onAbort exception', {
column: 14,
codeFrameText: 'onAbort',
message: 'bar is not a function',
})
verify('onRequest assertion failure', {
column: 29,
codeFrameText: 'onRequest',
message: `expected 'actual' to equal 'expected'`,
})
verify('onRequest exception', {
column: 14,
codeFrameText: 'onRequest',
message: 'bar is not a function',
})
verify('onResponse assertion failure', {
column: 29,
codeFrameText: 'onResponse',
message: `expected 'actual' to equal 'expected'`,
})
verify('onResponse exception', {
column: 14,
codeFrameText: 'onResponse',
message: 'bar is not a function',
})
})
it('cy.server', () => {
const verify = loadErrorSpec({
filePath: 'errors/server.cy.js',
failCount: 6,
})
verify('onAbort assertion failure', {
column: 29,
codeFrameText: 'onAbort',
message: `expected 'actual' to equal 'expected'`,
})
verify('onAbort exception', {
column: 14,
codeFrameText: 'onAbort',
message: 'bar is not a function',
})
verify('onRequest assertion failure', {
column: 29,
codeFrameText: 'onRequest',
message: `expected 'actual' to equal 'expected'`,
})
verify('onRequest exception', {
column: 14,
codeFrameText: 'onRequest',
message: 'bar is not a function',
})
verify('onResponse assertion failure', {
column: 29,
codeFrameText: 'onResponse',
message: `expected 'actual' to equal 'expected'`,
})
verify('onResponse exception', {
column: 14,
codeFrameText: 'onResponse',
message: 'bar is not a function',
})
})
it('cy.readFile', () => {
const verify = loadErrorSpec({
filePath: 'errors/readfile.cy.js',
@@ -347,13 +347,19 @@ describe('runner/cypress sessions.ui.spec', {
// cy.percySnapshot() // TODO: restore when Percy CSS is fixed. See https://github.com/cypress-io/cypress/issues/23435
})
describe('errors', () => {
describe('errors', { testIsolation: false }, () => {
describe('created session', () => {
before(() => {
loadSpec({
projectName: 'session-and-origin-e2e-specs',
filePath: 'session/errors.cy.js',
failCount: 7,
cy.then(async () => {
await Cypress.action('cy:url:changed', '')
await Cypress.action('cy:visit:blank', { type: 'on' })
})
.then(() => {
loadSpec({
projectName: 'session-and-origin-e2e-specs',
filePath: 'session/errors.cy.js',
failCount: 7,
})
})
})
@@ -423,11 +429,7 @@ describe('runner/cypress sessions.ui.spec', {
systemTestTitle: 'validate - throws an error',
errMessage: 'Something went wrong!',
},
].forEach((opts, index) => {
if (index !== 5) {
return
}
].forEach((opts) => {
const { testCase, systemTestTitle, errMessage } = opts
it(`has test error when validate ${testCase}`, () => {
@@ -544,20 +546,26 @@ describe('runner/cypress sessions.ui.spec', {
describe('successfully recreated session', () => {
before(() => {
loadSpec({
projectName: 'session-and-origin-e2e-specs',
filePath: 'session/errors.cy.js',
passCount: 7,
failCount: 0,
setup () {
cy.window().then((win) => {
cy.then(async () => {
await Cypress.action('cy:url:changed', '')
await Cypress.action('cy:visit:blank', { type: 'on' })
})
.then(() => {
loadSpec({
projectName: 'session-and-origin-e2e-specs',
filePath: 'session/errors.cy.js',
passCount: 7,
failCount: 0,
setup () {
cy.window().then((win) => {
// @ts-ignore
return win.CYPRESS_TEST_DATA = {
restoreSessionWithValidationFailure: true,
successfullyRecreatedSession: true,
}
})
},
return win.CYPRESS_TEST_DATA = {
restoreSessionWithValidationFailure: true,
successfullyRecreatedSession: true,
}
})
},
})
})
})
@@ -593,6 +601,10 @@ describe('runner/cypress sessions.ui.spec', {
errMessage: 'Something went wrong!',
},
].forEach(({ testCase, systemTestTitle, errMessage }, index) => {
if (index !== 0) {
return
}
it(`has test error when validate ${testCase}`, () => {
cy.contains('.test', systemTestTitle).as('example_test')
@@ -614,20 +626,26 @@ describe('runner/cypress sessions.ui.spec', {
describe('failed to recreated session', () => {
before(() => {
loadSpec({
projectName: 'session-and-origin-e2e-specs',
filePath: 'session/errors.cy.js',
passCount: 0,
failCount: 7,
setup () {
cy.window().then((win) => {
cy.then(async () => {
await Cypress.action('cy:url:changed', '')
await Cypress.action('cy:visit:blank', { type: 'on' })
})
.then(() => {
loadSpec({
projectName: 'session-and-origin-e2e-specs',
filePath: 'session/errors.cy.js',
passCount: 0,
failCount: 7,
setup () {
cy.window().then((win) => {
// @ts-ignore
return win.CYPRESS_TEST_DATA = {
restoreSessionWithValidationFailure: true,
successfullyRecreatedSession: false,
}
})
},
return win.CYPRESS_TEST_DATA = {
restoreSessionWithValidationFailure: true,
successfullyRecreatedSession: false,
}
})
},
})
})
})
-12
View File
@@ -202,18 +202,6 @@ describe('App: Settings', () => {
})
})
cy.get('[data-cy="experiment-experimentalSessionAndOrigin"]').within(() => {
cy.validateExternalLink({
name: 'cy.session()',
href: 'https://on.cypress.io/session',
})
cy.validateExternalLink({
name: 'cy.origin()',
href: 'https://on.cypress.io/origin',
})
})
cy.get('[data-cy="experiment-experimentalSourceRewriting"]').within(() => {
cy.validateExternalLink({
name: '#5273',
@@ -245,7 +245,7 @@ describe('App: Spec List (E2E)', () => {
cy.findByText('No specs matched your search:').should('not.be.visible')
})
// TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23305
// TODO: FIGURE OUT WHY THIS IS NOW FAILING CONSTANTLY
it.skip('saves the filter when navigating to a spec and back', function () {
const targetSpecFile = 'accounts_list.spec.js'
@@ -28,7 +28,7 @@ export const waitForSpecToFinish = (expectedResults, timeout?: number) => {
cy.get('.failed > .num').should('exist')
// Then ensure the tests are running
cy.contains('Your tests are loading...', { timeout: timeout || 20000 }).should('not.exist')
cy.contains('Your tests are loading...', { timeout: timeout || 30000 }).should('not.exist')
// Then ensure the tests have finished
cy.get('[aria-label="Rerun all tests"]', { timeout: timeout || 30000 })
+5 -5
View File
@@ -195,7 +195,7 @@ describe('App Top Nav Workflows', () => {
})
it('hides dropdown when version in header is clicked', () => {
cy.findByTestId('cypress-update-popover').findByRole('button', { expanded: false }).as('topNavVersionButton').click()
cy.findByTestId('cypress-update-popover').findAllByRole('button').first().as('topNavVersionButton').click()
cy.get('@topNavVersionButton').should('have.attr', 'aria-expanded', 'true')
@@ -541,7 +541,7 @@ describe('App Top Nav Workflows', () => {
cy.findByRole('button', { name: 'Log in' }).click()
})
cy.findByRole('dialog', { name: 'Log in to Cypress' }).within(() => {
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: 'Log in' }).click()
cy.contains('http://127.0.0.1:0000/redirect-to-auth').should('be.visible')
@@ -573,7 +573,7 @@ describe('App Top Nav Workflows', () => {
cy.findByRole('button', { name: 'Log in' }).click()
})
cy.findByRole('dialog', { name: 'Log in to Cypress' }).within(() => {
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: 'Log in' }).click()
cy.contains(loginText.titleFailed).should('be.visible')
@@ -623,7 +623,7 @@ describe('App Top Nav Workflows', () => {
cy.findByRole('button', { name: 'Log in' }).as('loginButton').click()
})
cy.findByRole('dialog', { name: 'Log in to Cypress' }).within(() => {
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: 'Log in' }).click()
cy.contains(loginText.titleFailed).should('be.visible')
@@ -660,7 +660,7 @@ describe('App Top Nav Workflows', () => {
cy.findByRole('button', { name: 'Log in' }).as('loginButton').click()
})
cy.findByRole('dialog', { name: 'Log in to Cypress' }).within(() => {
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: 'Log in' }).click()
cy.contains(loginText.titleFailed).should('be.visible')
cy.contains(loginText.bodyError).should('be.visible')
+1 -1
View File
@@ -29,7 +29,7 @@
"@intlify/vite-plugin-vue-i18n": "2.4.0",
"@packages/frontend-shared": "0.0.0-development",
"@percy/cypress": "^3.1.0",
"@testing-library/cypress": "8.0.0",
"@testing-library/cypress": "BlueWinds/cypress-testing-library#119054b5963b0d2e064b13c5cc6fc9db32c8b7b5",
"@types/faker": "5.5.8",
"@urql/core": "2.4.4",
"@urql/vue": "0.6.2",
+4 -23
View File
@@ -1,4 +1,4 @@
import { initial, session, sessionLifecycle, visitFailure } from './Blank'
import { initial, testIsolationBlankPage, visitFailure } from './Blank'
import { getContainerEl } from '@cypress/mount-utils'
describe('initial', () => {
@@ -13,28 +13,9 @@ describe('initial', () => {
})
})
describe('session', () => {
describe('testIsolationBlankPage', () => {
it('works', () => {
getContainerEl()!.innerHTML = session()
cy.get('[data-cy="cypress-logo"]')
cy.get('[data-cy="text"]').should('have.text', 'Default blank page')
cy.get('[data-cy="subtext"]').should('have.text', 'This page was cleared by navigating to about:blank.')
cy.percySnapshot()
})
it('works with small viewport', () => {
cy.viewport(200, 500)
getContainerEl()!.innerHTML = session()
cy.percySnapshot()
})
})
describe('sessionLifecycle', () => {
it('works', () => {
getContainerEl()!.innerHTML = sessionLifecycle()
getContainerEl()!.innerHTML = testIsolationBlankPage()
cy.get('[data-cy="cypress-logo"]')
cy.get('[data-cy="text"]').should('have.text', 'Default blank page')
@@ -45,7 +26,7 @@ describe('sessionLifecycle', () => {
it('works with small viewport', () => {
cy.viewport(200, 500)
getContainerEl()!.innerHTML = sessionLifecycle()
getContainerEl()!.innerHTML = testIsolationBlankPage()
cy.percySnapshot()
})
+2 -7
View File
@@ -127,15 +127,11 @@ export const initial = () => {
return blankContentsHtml()
}
export const sessionLifecycle = () => {
export const testIsolationBlankPage = () => {
return blankContentsHtml(blankPageHeader,
`${blankPageSubtext}<br>All active session data (cookies, localStorage and sessionStorage) across all domains are cleared.`)
}
export const session = () => {
return blankContentsHtml(blankPageHeader, blankPageSubtext)
}
export const visitFailure = (props) => {
const { status, statusText, contentType } = props
@@ -196,7 +192,6 @@ export const visitFailure = (props) => {
export const blankContents = {
initial,
session,
sessionLifecycle,
testIsolationBlankPage,
visitFailure,
}
+8 -17
View File
@@ -48,16 +48,12 @@ export class AutIframe {
this.$iframe.remove()
}
showInitialBlankContents () {
_showInitialBlankPage () {
this._showContents(blankContents.initial())
}
showSessionBlankContents () {
this._showContents(blankContents.session())
}
showSessionLifecycleBlankContents () {
this._showContents(blankContents.sessionLifecycle())
_showTestIsolationBlankPage () {
this._showContents(blankContents.testIsolationBlankPage())
}
showVisitFailure = (props) => {
@@ -126,7 +122,7 @@ export class AutIframe {
this.$iframe?.removeAttr('src')
}
visitBlank = ({ type }: { type?: 'session' | 'session-lifecycle' }) => {
visitBlankPage = (testIsolation?: boolean) => {
return new Promise<void>((resolve) => {
if (!this.$iframe) {
return
@@ -135,15 +131,10 @@ export class AutIframe {
this.$iframe[0].src = 'about:blank'
this.$iframe.one('load', () => {
switch (type) {
case 'session':
this.showSessionBlankContents()
break
case 'session-lifecycle':
this.showSessionLifecycleBlankContents()
break
default:
this.showInitialBlankContents()
if (testIsolation) {
this._showTestIsolationBlankPage()
} else {
this._showInitialBlankPage()
}
resolve()
@@ -53,7 +53,7 @@ export type SocketToDriverMap = {
}
export type DriverToLocalBus = {
'visit:blank': { type?: 'session' | 'session-lifecycle' }
'visit:blank': { testIsolation?: boolean }
'visit:failed': { status?: string, statusText: string, contentType?: () => string }
'page:loading': boolean
}
+2 -2
View File
@@ -232,7 +232,7 @@ function runSpecCT (config, spec: SpecFile) {
const specSrc = getSpecUrl(config.namespace, spec.absolute)
autIframe.showInitialBlankContents()
autIframe._showInitialBlankPage()
$autIframe.prop('src', specSrc)
// initialize Cypress (driver) with the AUT!
@@ -290,7 +290,7 @@ function runSpecE2E (config, spec: SpecFile) {
el.remove()
})
autIframe.showInitialBlankContents()
autIframe.visitBlankPage()
// create Spec IFrame
const specSrc = getSpecUrl(config.namespace, encodeURIComponent(spec.relative))
+2 -2
View File
@@ -47,8 +47,8 @@ export function useEventManager () {
getAutIframeModel().reattachStudio()
})
eventManager.on('visit:blank', ({ type }) => {
getAutIframeModel().visitBlank({ type })
eventManager.on('visit:blank', ({ testIsolation }) => {
getAutIframeModel().visitBlankPage(testIsolation)
})
eventManager.on('run:end', () => {
+2 -2
View File
@@ -22,7 +22,7 @@ describe('SpecItem', () => {
const parentColor = getComputedStyle($el.parent()[0]).color
const highlightedElementColor = getComputedStyle($el[0]).color
cy.wrap(highlightedElementColor).should('not.equal', parentColor)
expect(highlightedElementColor).not.to.equal(parentColor)
})
})
@@ -35,7 +35,7 @@ describe('SpecItem', () => {
const parentColor = getComputedStyle($el.parent()[0]).color
const highlightedElementColor = getComputedStyle($el[0]).color
cy.wrap(highlightedElementColor).should('equal', parentColor)
expect(highlightedElementColor).to.equal(parentColor)
})
})
@@ -6,6 +6,7 @@ exports['config/src/index .getBreakingKeys returns list of breaking config keys
'experimentalNetworkStubbing',
'experimentalRunEvents',
'experimentalSessionSupport',
'experimentalSessionAndOrigin',
'experimentalShadowDomSupport',
'firefoxGcInterval',
'ignoreTestFiles',
@@ -36,8 +37,8 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'experimentalFetchPolyfill': false,
'experimentalInteractiveRunEvents': false,
'experimentalRunAllSpecs': false,
'experimentalSessionAndOrigin': false,
'experimentalModifyObstructiveThirdPartyCode': false,
'experimentalOriginDependencies': false,
'experimentalSourceRewriting': false,
'experimentalSingleTabRunMode': false,
'experimentalStudio': false,
@@ -70,7 +71,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'supportFile': 'cypress/support/e2e.{js,jsx,ts,tsx}',
'supportFolder': false,
'taskTimeout': 60000,
'testIsolation': null,
'testIsolation': true,
'trashAssetsBeforeRuns': true,
'userAgent': null,
'video': true,
@@ -99,7 +100,6 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
'socketId': null,
'socketIoCookie': '__socket',
'socketIoRoute': '/__socket',
'xhrRoute': '/xhrs/',
}
exports['config/src/index .getDefaultValues returns list of public config keys for selected testing type 1'] = {
@@ -122,8 +122,8 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'experimentalFetchPolyfill': false,
'experimentalInteractiveRunEvents': false,
'experimentalRunAllSpecs': false,
'experimentalSessionAndOrigin': false,
'experimentalModifyObstructiveThirdPartyCode': false,
'experimentalOriginDependencies': false,
'experimentalSourceRewriting': false,
'experimentalSingleTabRunMode': false,
'experimentalStudio': false,
@@ -156,7 +156,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'supportFile': 'cypress/support/e2e.{js,jsx,ts,tsx}',
'supportFolder': false,
'taskTimeout': 60000,
'testIsolation': null,
'testIsolation': true,
'trashAssetsBeforeRuns': true,
'userAgent': null,
'video': true,
@@ -185,7 +185,6 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
'socketId': null,
'socketIoCookie': '__socket',
'socketIoRoute': '/__socket',
'xhrRoute': '/xhrs/',
}
exports['config/src/index .getPublicConfigKeys returns list of public config keys 1'] = [
@@ -204,8 +203,8 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
'experimentalFetchPolyfill',
'experimentalInteractiveRunEvents',
'experimentalRunAllSpecs',
'experimentalSessionAndOrigin',
'experimentalModifyObstructiveThirdPartyCode',
'experimentalOriginDependencies',
'experimentalSourceRewriting',
'experimentalSingleTabRunMode',
'experimentalStudio',
-2
View File
@@ -165,8 +165,6 @@ export const validate = (cfg: any, onErr: (property: ErrResult | string) => void
if (validationFn && value !== defaultValues[key]) {
const result = validationFn(key, value, {
testingType,
// TODO: remove with experimentalSessionAndOrigin. Fixed with: https://github.com/cypress-io/cypress/issues/21471
experimentalSessionAndOrigin: cfg.e2e?.experimentalSessionAndOrigin || cfg.experimentalSessionAndOrigin,
})
if (result !== true) {
+28 -53
View File
@@ -21,6 +21,7 @@ const BREAKING_OPTION_ERROR_KEY: Readonly<AllCypressErrorNames[]> = [
'EXPERIMENTAL_NETWORK_STUBBING_REMOVED',
'EXPERIMENTAL_RUN_EVENTS_REMOVED',
'EXPERIMENTAL_SESSION_SUPPORT_REMOVED',
'EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED',
'EXPERIMENTAL_SINGLE_TAB_RUN_MODE',
'EXPERIMENTAL_SHADOW_DOM_REMOVED',
'FIREFOX_GC_INTERVAL_REMOVED',
@@ -33,7 +34,6 @@ const BREAKING_OPTION_ERROR_KEY: Readonly<AllCypressErrorNames[]> = [
type ValidationOptions = {
testingType: TestingType | null
experimentalSessionAndOrigin: boolean
}
export type BreakingOptionErrorKey = typeof BREAKING_OPTION_ERROR_KEY[number]
@@ -209,18 +209,17 @@ const driverConfigOptions: Array<DriverConfigOption> = [
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
}, {
// TODO: remove with experimentalSessionAndOrigin. Fixed with: https://github.com/cypress-io/cypress/issues/21471
name: 'experimentalSessionAndOrigin',
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
}, {
name: 'experimentalModifyObstructiveThirdPartyCode',
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
requireRestartOnChange: 'server',
}, {
name: 'experimentalOriginDependencies',
defaultValue: false,
validation: validate.isBoolean,
isExperimental: true,
}, {
name: 'experimentalSourceRewriting',
defaultValue: false,
@@ -382,38 +381,17 @@ const driverConfigOptions: Array<DriverConfigOption> = [
overrideLevel: 'any',
}, {
name: 'testIsolation',
// TODO: https://github.com/cypress-io/cypress/issues/23093
// When experimentalSessionAndOrigin is removed and released as GA,
// update the defaultValue from undefined to 'on' and
// update this code to remove the check/override specific to enable
// 'on' by default when experimentalSessionAndOrigin=true
defaultValue: (options: Record<string, any> = {}) => {
if (options.testingType === 'component') {
return null
}
return options?.experimentalSessionAndOrigin || options?.config?.e2e?.experimentalSessionAndOrigin ? 'on' : null
},
defaultValue: true,
validation: (key: string, value: any, opts: ValidationOptions) => {
const { testingType, experimentalSessionAndOrigin } = opts
const { testingType } = opts
if (testingType == null || testingType === 'component') {
return true
let configOpts = [true, false]
if (testingType === 'component') {
configOpts.pop()
}
if (experimentalSessionAndOrigin && testingType === 'e2e') {
return validate.isOneOf('on', 'off')(key, value)
}
if (value == null) {
return true
}
return {
key,
value,
type: 'not set unless the experimentalSessionAndOrigin flag is turned on',
}
return validate.isOneOf(...configOpts)(key, value)
},
overrideLevel: 'suite',
}, {
@@ -569,11 +547,6 @@ const runtimeOptions: Array<RuntimeConfigOption> = [
defaultValue: pkg.version,
validation: validate.isString,
isInternal: true,
}, {
name: 'xhrRoute',
defaultValue: '/xhrs/',
validation: validate.isString,
isInternal: true,
},
]
@@ -615,6 +588,10 @@ export const breakingOptions: Readonly<BreakingOption[]> = [
name: 'experimentalSessionSupport',
errorKey: 'EXPERIMENTAL_SESSION_SUPPORT_REMOVED',
isWarning: true,
}, {
name: 'experimentalSessionAndOrigin',
errorKey: 'EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED',
isWarning: true,
}, {
name: 'experimentalShadowDomSupport',
errorKey: 'EXPERIMENTAL_SHADOW_DOM_REMOVED',
@@ -660,11 +637,6 @@ export const breakingRootOptions: Array<BreakingOption> = [
errorKey: 'CONFIG_FILE_INVALID_ROOT_CONFIG_E2E',
isWarning: false,
testingTypes: ['e2e'],
}, {
name: 'experimentalSessionAndOrigin',
errorKey: 'CONFIG_FILE_INVALID_ROOT_CONFIG_E2E',
isWarning: false,
testingTypes: ['e2e'],
}, {
name: 'excludeSpecPattern',
errorKey: 'CONFIG_FILE_INVALID_ROOT_CONFIG',
@@ -701,6 +673,12 @@ export const breakingRootOptions: Array<BreakingOption> = [
isWarning: false,
testingTypes: ['e2e'],
},
{
name: 'experimentalOriginDependencies',
errorKey: 'EXPERIMENTAL_ORIGIN_DEPENDENCIES_E2E_ONLY',
isWarning: false,
testingTypes: ['e2e'],
},
]
export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component: Array<BreakingOption> } = {
@@ -709,7 +687,6 @@ export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component
name: 'experimentalSingleTabRunMode',
errorKey: 'EXPERIMENTAL_SINGLE_TAB_RUN_MODE',
isWarning: false,
testingTypes: ['e2e'],
},
{
name: 'indexHtmlFile',
@@ -723,16 +700,10 @@ export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component
errorKey: 'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_COMPONENT',
isWarning: false,
},
{
name: 'experimentalSessionAndOrigin',
errorKey: 'CONFIG_FILE_INVALID_TESTING_TYPE_CONFIG_COMPONENT',
isWarning: false,
},
{
name: 'experimentalStudio',
errorKey: 'EXPERIMENTAL_STUDIO_E2E_ONLY',
isWarning: false,
testingTypes: ['component'],
},
{
name: 'testIsolation',
@@ -743,7 +714,11 @@ export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component
name: 'experimentalRunAllSpecs',
errorKey: 'EXPERIMENTAL_RUN_ALL_SPECS_E2E_ONLY',
isWarning: false,
testingTypes: ['component'],
},
{
name: 'experimentalOriginDependencies',
errorKey: 'EXPERIMENTAL_ORIGIN_DEPENDENCIES_E2E_ONLY',
isWarning: false,
},
],
}
-16
View File
@@ -403,8 +403,6 @@ export function mergeDefaults (
const defaultsForRuntime = getDefaultValues({
...options,
// TODO: clean this up. Fixed with: https://github.com/cypress-io/cypress/issues/21471
experimentalSessionAndOrigin: config.experimentalSessionAndOrigin,
})
_.defaultsDeep(config, defaultsForRuntime)
@@ -480,20 +478,6 @@ export function mergeDefaults (
throw makeConfigError(errors.get(err, ...args))
}, testingType)
// TODO: https://github.com/cypress-io/cypress/issues/23093
// testIsolation should equal 'on' by default when experimentalSessionAndOrigin=true
// Once experimentalSessionAndOrigin is made GA, remove this logic and update the defaultValue
// to be be 'on'
if (testingType === 'e2e' && config.experimentalSessionAndOrigin) {
if (config.rawJson.testIsolation) {
config.resolved.testIsolation.from = 'config'
} else {
config.testIsolation = 'on'
config.resolved.testIsolation.value = 'on'
config.resolved.testIsolation.from === 'default'
}
}
// We need to remove the nested propertied by testing type because it has been
// flattened/compacted based on the current testing type that is selected
// making the config only available with the properties that are valid,
-1
View File
@@ -34,7 +34,6 @@ export function setUrls (obj: any) {
proxyUrl,
browserUrl: rootUrl + obj.clientRoute,
reporterUrl: rootUrl + obj.reporterRoute,
xhrUrl: `${obj.namespace}${obj.xhrRoute}`,
}
}
+2 -2
View File
@@ -195,7 +195,7 @@ describe('config/src/index', () => {
const isSuiteOverride = true
configUtil.validateOverridableAtRunTime({ testIsolation: 'on' }, isSuiteOverride, errorFn)
configUtil.validateOverridableAtRunTime({ testIsolation: true }, isSuiteOverride, errorFn)
expect(errorFn).to.have.callCount(0)
})
@@ -205,7 +205,7 @@ describe('config/src/index', () => {
const isSuiteOverride = false
configUtil.validateOverridableAtRunTime({ testIsolation: false }, isSuiteOverride, errorFn)
configUtil.validateOverridableAtRunTime({ testIsolation: 'off' }, isSuiteOverride, errorFn)
expect(errorFn).to.have.callCount(1)
expect(errorFn).to.have.been.calledWithMatch({
+17 -35
View File
@@ -972,6 +972,16 @@ describe('config/src/project/utils', () => {
expect(warning).to.be.calledWith('EXPERIMENTAL_SESSION_SUPPORT_REMOVED')
})
it('warns if experimentalSessionAndOrigin is passed', async function () {
const warning = sinon.spy(errors, 'warning')
await this.defaults('experimentalSessionAndOrigin', true, {
experimentalSessionAndOrigin: true,
})
expect(warning).to.be.calledWith('EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED')
})
it('warns if experimentalShadowDomSupport is passed', async function () {
const warning = sinon.spy(errors, 'warning')
@@ -1044,8 +1054,8 @@ describe('config/src/project/utils', () => {
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalOriginDependencies: { value: false, from: 'default' },
experimentalRunAllSpecs: { value: false, from: 'default' },
experimentalSessionAndOrigin: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalStudio: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
@@ -1079,7 +1089,7 @@ describe('config/src/project/utils', () => {
supportFile: { value: false, from: 'config' },
supportFolder: { value: false, from: 'default' },
taskTimeout: { value: 60000, from: 'default' },
testIsolation: { value: null, from: 'default' },
testIsolation: { value: true, from: 'default' },
trashAssetsBeforeRuns: { value: true, from: 'default' },
userAgent: { value: null, from: 'default' },
video: { value: true, from: 'default' },
@@ -1139,8 +1149,8 @@ describe('config/src/project/utils', () => {
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalOriginDependencies: { value: false, from: 'default' },
experimentalRunAllSpecs: { value: false, from: 'default' },
experimentalSessionAndOrigin: { value: false, from: 'default' },
experimentalSingleTabRunMode: { value: false, from: 'default' },
experimentalStudio: { value: false, from: 'default' },
experimentalSourceRewriting: { value: false, from: 'default' },
@@ -1196,7 +1206,7 @@ describe('config/src/project/utils', () => {
supportFile: { value: false, from: 'config' },
supportFolder: { value: false, from: 'default' },
taskTimeout: { value: 60000, from: 'default' },
testIsolation: { value: null, from: 'default' },
testIsolation: { value: true, from: 'default' },
trashAssetsBeforeRuns: { value: true, from: 'default' },
userAgent: { value: null, from: 'default' },
video: { value: true, from: 'default' },
@@ -1212,14 +1222,14 @@ describe('config/src/project/utils', () => {
})
})
it('sets testIsolation=on by default when experimentalSessionAndOrigin=true and e2e testing', () => {
it('honors user config for testIsolation', () => {
sinon.stub(utils, 'getProcessEnvVars').returns({})
const obj = {
projectRoot: '/foo/bar',
supportFile: false,
baseUrl: 'http://localhost:8080',
experimentalSessionAndOrigin: true,
testIsolation: false,
}
const options = {
@@ -1230,36 +1240,8 @@ describe('config/src/project/utils', () => {
return mergeDefaults(obj, options, {}, getFilesByGlob)
.then((cfg) => {
expect(cfg.resolved).to.have.property('experimentalSessionAndOrigin')
expect(cfg.resolved.experimentalSessionAndOrigin).to.deep.eq({ value: true, from: 'config' })
expect(cfg.resolved).to.have.property('testIsolation')
expect(cfg.resolved.testIsolation).to.deep.eq({ value: 'on', from: 'default' })
})
})
it('honors user config for testIsolation when experimentalSessionAndOrigin=true and e2e testing', () => {
sinon.stub(utils, 'getProcessEnvVars').returns({})
const obj = {
projectRoot: '/foo/bar',
supportFile: false,
baseUrl: 'http://localhost:8080',
experimentalSessionAndOrigin: true,
testIsolation: 'on',
}
const options = {
testingType: 'e2e',
}
const getFilesByGlob = sinon.stub().returns(['path/to/file.ts'])
return mergeDefaults(obj, options, {}, getFilesByGlob)
.then((cfg) => {
expect(cfg.resolved).to.have.property('experimentalSessionAndOrigin')
expect(cfg.resolved.experimentalSessionAndOrigin).to.deep.eq({ value: true, from: 'config' })
expect(cfg.resolved).to.have.property('testIsolation')
expect(cfg.resolved.testIsolation).to.deep.eq({ value: 'on', from: 'config' })
expect(cfg.resolved.testIsolation).to.deep.eq({ value: false, from: 'config' })
})
})
})
@@ -28,7 +28,6 @@ module.exports = defineConfig({
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'localhost:3000',
experimentalSessionAndOrigin: true,
},
component: {
setupNodeEvents(on, config) {},
@@ -69,7 +68,6 @@ module.exports = defineConfig({
},
retries: 2,
baseUrl: 'localhost:300',
experimentalSessionAndOrigin: true,
slowTestThreshold: 500,
},
component: {
@@ -53,7 +53,6 @@ export class HtmlDataSource {
'testingType',
'componentTesting',
'reporterUrl',
'xhrUrl',
'namespace',
'socketIoRoute',
]
@@ -495,7 +495,6 @@ export function reduceConfig (cfg: LegacyCypressConfigJson, options: CreateConfi
e2e: { ...acc.e2e, supportFile: val },
}
case 'baseUrl':
case 'experimentalSessionAndOrigin':
return {
...acc,
e2e: { ...acc.e2e, [key]: val },
@@ -96,11 +96,6 @@ const resolvedOptions: Array<ResolvedConfigOption> = [
defaultValue: false,
isExperimental: true,
canUpdateDuringTestTime: false,
}, {
name: 'experimentalSessionAndOrigin',
defaultValue: false,
isExperimental: true,
canUpdateDuringTestTime: true,
}, {
name: 'experimentalSourceRewriting',
defaultValue: false,
@@ -60,7 +60,6 @@ describe('cypress.config.js generation', () => {
const config: Partial<Cypress.Config> = {
e2e: {
baseUrl: 'localhost:3000',
experimentalSessionAndOrigin: true,
},
}
@@ -127,7 +126,6 @@ describe('cypress.config.js generation', () => {
const config = {
viewportWidth: 300,
baseUrl: 'localhost:300',
experimentalSessionAndOrigin: true,
slowTestThreshold: 500,
e2e: {
retries: 2,
+4 -12
View File
@@ -6,8 +6,9 @@ The goal of this document is to give a technical overview of the architecture be
See [Node.jss URL doc](https://nodejs.org/api/url.html#url-strings-and-url-objects) for a handy breakdown of URL parts
- **domain**: A hostname without the subdomain (for the purposes of this doc). May also be referred to as **superdomain** (e.g. `example.com`, `example.co.uk`, `localhost`)
- **origin**: The combination of the protocol, hostname, and port of a URL. For the purposes of Cypress, the subdomain is irrelevant. (e.g. `http://example.com:3500`)
- **domain**: The hostname portion of a URL. (e.g. `example.com`, `www.example.com`, `www.example.co.uk`, `localhost`)
- **superdomain**: A domain without the subdomain. (e.g. `example.com`, `example.co.uk`, `localhost`)
- **origin**: The combination of the protocol, hostname, and port of a URL (e.g. `http://www.example.com:3500`)
- **top**: The main window/frame of the browser
- **primary origin**: The origin that top is on
- **secondary origin**: Any origin that is not the primary origin
@@ -209,13 +210,4 @@ Nesting **cy.origin()** inside the callback is not currently supported, but supp
### cy.intercept()
Most use-cases for **cy.intercept()** can be accomplished by using it outside of the **cy.origin()** callback. Since there may be use-cases where setting up a response, for example, requires the scope within the **cy.origin()** callback, we will likely [add support for **cy.intercept()** in the future](https://github.com/cypress-io/cypress/issues/20720).
### Deprecated commands / methods
All deprecated APIs are not supported in the **cy.origin()** callback and we do not plan to ever add support for them. If a user attempts to use one, we throw an error that points them to the preferred API that superseded it. The following are deprecated APIs that are not supported:
- **cy.route()**: Superseded by **cy.intercept()**
- **cy.server()**: Superseded by **cy.intercept()**
- **Cypress.Server.defaults()**: Superseded by **cy.intercept()**
- **Cypress.Cookies.preserveOnce()**: Superseded by sessions
Most use-cases for **cy.intercept()** can be accomplished by using it outside of the **cy.origin()** callback. Since there may be use-cases where setting up a response, for example, requires the scope within the **cy.origin()** callback, we will likely [add support for **cy.intercept()** in the future](https://github.com/cypress-io/cypress/issues/20720).
+2
View File
@@ -18,6 +18,8 @@ export default defineConfig({
configFile: '../../mocha-reporter-config.json',
},
e2e: {
experimentalOriginDependencies: true,
experimentalModifyObstructiveThirdPartyCode: true,
setupNodeEvents: (on, config) => {
return require('./cypress/plugins')(on, config)
},
@@ -98,6 +98,27 @@ describe('src/cy/commands/actions/check', () => {
cy.get(checkbox).check()
})
it('requeries if the DOM rerenders during actionability', () => {
cy.$$('[name=colors]').first().prop('disabled', true)
const listener = _.after(3, () => {
cy.$$('[name=colors]').first().prop('disabled', false)
const parent = cy.$$('[name=colors]').parent()
parent.replaceWith(parent[0].outerHTML)
cy.off('command:retry', listener)
})
cy.on('command:retry', listener)
cy.get('[name=colors]').check().then(($inputs) => {
$inputs.each((i, el) => {
expect($(el)).to.be.checked
})
})
})
// readonly should only be limited to inputs, not checkboxes
it('can check readonly checkboxes', () => {
cy.get('#readonly-checkbox').check().then(($checkbox) => {
@@ -302,24 +323,27 @@ describe('src/cy/commands/actions/check', () => {
})
it('can set options.waitForAnimations', () => {
cy.stub(cy, 'ensureElementIsNotAnimating').throws(new Error('animating!'))
let retries = 0
cy.get(':checkbox:first').check({ waitForAnimations: false }).then(() => {
expect(cy.ensureElementIsNotAnimating).not.to.be.called
cy.on('command:retry', () => {
retries += 1
})
cy.get(':checkbox:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).check({ waitForAnimations: false }).then(() => {
expect(retries).to.eq(0)
})
})
it('can set options.animationDistanceThreshold', () => {
const $btn = cy.$$(':checkbox:first')
let retries = 0
cy.spy(cy, 'ensureElementIsNotAnimating')
cy.get(':checkbox:first').check({ animationDistanceThreshold: 1000 }).then(() => {
const { fromElWindow } = Cypress.dom.getElementCoordinatesByPosition($btn)
const { args } = cy.ensureElementIsNotAnimating.firstCall
cy.on('command:retry', () => {
retries += 1
})
expect(args[1]).to.deep.eq([fromElWindow, fromElWindow])
expect(args[2]).to.eq(1000)
cy.get(':checkbox:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).check({ animationDistanceThreshold: 1000 }).then(() => {
// One retry, because $actionability always waits for two sets of points to determine if an element is animating.
expect(retries).to.eq(1)
})
})
@@ -437,7 +461,7 @@ describe('src/cy/commands/actions/check', () => {
cy.on('fail', (err) => {
expect(checked).to.eq(1)
expect(err.message).to.include('`cy.check()` failed because this element')
expect(err.message).to.include('`cy.check()` failed because the page updated')
done()
})
@@ -1079,7 +1103,7 @@ describe('src/cy/commands/actions/check', () => {
cy.on('fail', (err) => {
expect(unchecked).to.eq(1)
expect(err.message).to.include('`cy.uncheck()` failed because this element')
expect(err.message).to.include('`cy.uncheck()` failed because the page updated')
done()
})
@@ -52,6 +52,26 @@ describe('src/cy/commands/actions/type - #clear', () => {
})
})
it('requeries if the DOM rerenders during actionability', () => {
const clicked = cy.stub()
const retried = cy.stub()
const textarea = cy.$$('#comments').val('foo bar').prop('disabled', true)
cy.on('command:retry', _.after(3, () => {
if (!retried.callCount) {
textarea.replaceWith(textarea[0].outerHTML)
cy.$$('#comments').prop('disabled', false).on('click', clicked)
retried()
}
}))
cy.get('#comments').clear().then(() => {
expect(clicked).to.be.calledOnce
expect(retried).to.be.called
})
})
it('can force clear even when being covered by another element', () => {
const $input = $('<input />')
.attr('id', 'input-covered-in-span')
@@ -275,7 +295,7 @@ describe('src/cy/commands/actions/type - #clear', () => {
cy.on('fail', (err) => {
expect(cleared).to.be.calledOnce
expect(err.message).to.include('`cy.clear()` failed because this element')
expect(err.message).to.include('`cy.clear()` failed because the page updated')
done()
})
@@ -738,6 +738,27 @@ describe('src/cy/commands/actions/click', () => {
})
})
it('requeries if the DOM rerenders during actionability', () => {
cy.$$('[name=colors]').first().prop('disabled', true)
const listener = _.after(3, () => {
cy.$$('[name=colors]').first().prop('disabled', false)
const parent = cy.$$('[name=colors]').parent()
parent.replaceWith(parent[0].outerHTML)
cy.off('command:retry', listener)
})
cy.on('command:retry', listener)
cy.get('[name=colors]').first().click().then(($inputs) => {
$inputs.each((i, el) => {
expect($(el)).to.be.checked
})
})
})
it('increases the timeout delta after each click', () => {
const count = cy.$$('#three-buttons button').length
@@ -813,22 +834,20 @@ describe('src/cy/commands/actions/click', () => {
})
it('places cursor at the end of [contenteditable]', () => {
cy.get('[contenteditable]:first')
.invoke('html', '<div><br></div>').click()
.then(expectCaret(0))
cy.get('[contenteditable]:first').as('edit')
cy.get('[contenteditable]:first')
.invoke('html', 'foo').click()
.then(expectCaret(3))
cy.get('@edit').invoke('html', '<div><br></div>')
cy.get('@edit').click().then(expectCaret(0))
cy.get('[contenteditable]:first')
.invoke('html', '<div>foo</div>').click()
.then(expectCaret(3))
cy.get('@edit').invoke('html', 'foo')
cy.get('@edit').click().then(expectCaret(3))
cy.get('@edit').invoke('html', '<div>foo</div>')
cy.get('@edit').click().then(expectCaret(3))
cy.get('[contenteditable]:first')
// firefox headless: prevent contenteditable from disappearing (dont set to empty)
.invoke('html', '<br>').click()
.then(expectCaret(0))
cy.get('@edit').invoke('html', '<br>')
cy.get('@edit').click().then(expectCaret(0))
})
it('can click SVG elements', () => {
@@ -1034,6 +1053,15 @@ describe('src/cy/commands/actions/click', () => {
})
describe('actionability', () => {
let retries = 0
beforeEach(() => {
retries = 0
cy.on('command:retry', () => {
retries += 1
})
})
it('can click on inline elements that wrap lines', () => {
cy.get('#overflow-link').find('.wrapped').click()
})
@@ -1320,24 +1348,19 @@ describe('src/cy/commands/actions/click', () => {
$('<span>span on button</span>').css({ position: 'absolute', left: $btn.offset().left, top: $btn.offset().top, padding: 5, display: 'inline-block', backgroundColor: 'yellow' }).prependTo(cy.$$('body'))
const scrolled = []
let retried = false
let clicked = false
cy.on('scrolled', ($el, type) => {
scrolled.push(type)
})
cy.on('command:retry', () => {
retried = true
})
$btn.on('click', () => {
clicked = true
})
cy.get('#button-covered-in-span').click({ force: true }).then(() => {
expect(scrolled).to.be.empty
expect(retried).to.be.false
expect(retries).to.eq(0)
expect(clicked).to.be.true
})
@@ -1348,19 +1371,14 @@ describe('src/cy/commands/actions/click', () => {
$('<span>span on button</span>').css({ opacity: 0, position: 'absolute', left: $btn.offset().left, top: $btn.offset().top, padding: 5, display: 'inline-block' }).prependTo(cy.$$('body'))
let retried = false
let clicked = false
cy.on('command:retry', () => {
retried = true
})
$btn.on('click', () => {
clicked = true
})
cy.get('#button-covered-in-span').click({ force: true }).then(() => {
expect(retried).to.be.false
expect(retries).to.be.eq(0)
expect(clicked).to.be.true
})
})
@@ -1380,7 +1398,6 @@ describe('src/cy/commands/actions/click', () => {
}).prependTo(cy.$$('body'))
const scrolled = []
let retried = false
cy.on('scrolled', ($el, type) => {
scrolled.push(type)
@@ -1388,11 +1405,10 @@ describe('src/cy/commands/actions/click', () => {
cy.on('command:retry', _.after(3, () => {
$span.hide()
retried = true
}))
cy.get('#button-covered-in-span').click().then(() => {
expect(retried).to.be.true
expect(retries).to.be.gt(0)
// - element scrollIntoView
// - element scrollIntoView (retry animation coords)
@@ -1553,22 +1569,18 @@ describe('src/cy/commands/actions/click', () => {
it('waits until element becomes visible', () => {
const $btn = cy.$$('#button').hide()
let retried = false
cy.on('command:retry', _.after(3, () => {
$btn.show()
retried = true
}))
cy.get('#button').click().then(() => {
expect(retried).to.be.true
expect(retries).to.be.gt(0)
})
})
it('waits until element is no longer disabled', () => {
const $btn = cy.$$('#button').prop('disabled', true)
let retried = false
let clicks = 0
$btn.on('click', () => {
@@ -1577,83 +1589,64 @@ describe('src/cy/commands/actions/click', () => {
cy.on('command:retry', _.after(3, () => {
$btn.prop('disabled', false)
retried = true
}))
cy.get('#button').click().then(() => {
expect(clicks).to.eq(1)
expect(retried).to.be.true
expect(retries).to.be.gt(0)
})
})
it('succeeds when DOM rerenders and returns new subject', () => {
const $btn = cy.$$('#button').prop('disabled', true)
cy.on('command:retry', _.after(3, () => {
$btn.replaceWith('<button id="button">New Button</button>')
}))
cy.get('#button').click().should('contain', 'New Button')
})
it('waits until element stops animating', () => {
let retries = 0
cy.on('command:retry', () => {
retries += 1
})
cy.stub(cy, 'ensureElementIsNotAnimating')
.throws(new Error('animating!'))
.onThirdCall().returns()
cy.get('button:first').click().then(() => {
// - retry animation coords
// - retry animation
// - retry animation
expect(retries).to.eq(3)
expect(cy.ensureElementIsNotAnimating).to.be.calledThrice
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).click().then(() => {
expect(retries).to.gt(1)
})
})
it('does not throw when waiting for animations is disabled', {
waitForAnimations: false,
}, () => {
cy.stub(cy, 'ensureElementIsNotAnimating').throws(new Error('animating!'))
cy.get('button:first').click().then(() => {
expect(cy.ensureElementIsNotAnimating).not.to.be.called
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).click().then(() => {
expect(retries).to.eq(0)
})
})
it('does not throw when turning off waitForAnimations in options', () => {
cy.stub(cy, 'ensureElementIsNotAnimating').throws(new Error('animating!'))
cy.get('button:first').click({ waitForAnimations: false }).then(() => {
expect(cy.ensureElementIsNotAnimating).not.to.be.called
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).click({ waitForAnimations: false }).then(() => {
expect(retries).to.eql(0)
})
})
it('passes options.animationDistanceThreshold to cy.ensureElementIsNotAnimating', () => {
const $btn = cy.$$('button:first')
cy.spy(cy, 'ensureElementIsNotAnimating')
cy.get('button:first').click({ animationDistanceThreshold: 1000 }).then(() => {
const { fromElWindow } = Cypress.dom.getElementCoordinatesByPosition($btn)
const { args } = cy.ensureElementIsNotAnimating.firstCall
expect(args[1]).to.deep.eq([fromElWindow, fromElWindow])
expect(args[2]).to.eq(1000)
it('passes options.animationDistanceThreshold to $actionability.ensureElIsNotAnimating', () => {
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).click({ animationDistanceThreshold: 1000 }).then(() => {
// One retry, because $actionability waits for two sets of position coordinates.
expect(retries).to.eq(1)
})
})
it('passes config.animationDistanceThreshold to cy.ensureElementIsNotAnimating', () => {
const animationDistanceThreshold = Cypress.config('animationDistanceThreshold')
it('passes config.animationDistanceThreshold to $actionability.ensureElIsNotAnimating', () => {
let old = Cypress.config('animationDistanceThreshold')
const $btn = cy.$$('button:first')
Cypress.config('animationDistanceThreshold', 1000)
cy.spy(cy, 'ensureElementIsNotAnimating')
cy.get('button:first').click().then(() => {
const { fromElWindow } = Cypress.dom.getElementCoordinatesByPosition($btn)
const { args } = cy.ensureElementIsNotAnimating.firstCall
expect(args[1]).to.deep.eq([fromElWindow, fromElWindow])
expect(args[2]).to.eq(animationDistanceThreshold)
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).click().then(() => {
try {
// One retry, because $actionability waits for two sets of position coordinates.
expect(retries).to.eq(1)
} finally {
Cypress.config('animationDistanceThreshold', old)
}
})
})
@@ -2110,41 +2103,21 @@ describe('src/cy/commands/actions/click', () => {
cy.get('.badge-multi').click()
})
it('throws when subject is not in the document', (done) => {
let clicked = 0
const $checkbox = cy.$$(':checkbox:first').click(() => {
clicked += 1
$checkbox.remove()
return false
})
cy.on('fail', (err) => {
expect(clicked).to.eq(1)
expect(err.message).to.include('`cy.click()` failed because this element is detached from the DOM')
done()
})
cy.get(':checkbox:first').click().click()
})
// This is an instance of an unfixable detached DOM error: .then() is a command, so it sets the subject to a
// *specific element*, which then gets detached.
// The error message tells the user exactly how to fix this case.
it('throws when subject is detached during actionability', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.click()` failed because this element is detached from the DOM')
expect(err.message).to.include('`cy.click()` failed because the page updated while this command was executing.')
expect(err.message).to.include('You can typically solve this by breaking up a chain.')
done()
})
cy.get('input:first')
.then(($el) => {
// This represents an asynchronous re-render
// since we fire the 'scrolled' event during actionability
// if we use el.on('scroll'), headless electron is flaky
cy.on('scrolled', () => {
$el.remove()
})
cy.on('scrolled', () => $el.remove())
})
.click()
})
@@ -3259,26 +3232,6 @@ describe('src/cy/commands/actions/click', () => {
cy.dblclick()
})
it('throws when subject is not in the document', (done) => {
let dblclicked = 0
const $button = cy.$$('button:first').dblclick(() => {
dblclicked += 1
$button.remove()
return false
})
cy.on('fail', (err) => {
expect(dblclicked).to.eq(1)
expect(err.message).to.include('`cy.dblclick()` failed because this element')
done()
})
cy.get('button:first').dblclick().dblclick()
})
it('logs once when not dom subject', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
@@ -3350,21 +3303,6 @@ describe('src/cy/commands/actions/click', () => {
})
})
// TODO: remove this after 4.0 when {multiple:true} is no longer default
// https://github.com/cypress-io/cypress/issues/5406
it('does not log default option {multiple:true}', () => {
const logs = []
cy.on('log:added', (attrs, log) => {
logs.push(log)
})
cy.get('button:first').dblclick().then(() => {
expect(logs[1].get('message')).to.eq('')
expect(logs[1].invoke('consoleProps').Options).not.ok
})
})
it('returns only the $el for the element of the subject that was dblclicked', () => {
const dblclicks = []
@@ -3698,26 +3636,6 @@ describe('src/cy/commands/actions/click', () => {
cy.rightclick()
})
it('throws when subject is not in the document', (done) => {
let rightclicked = 0
const $button = cy.$$('button:first').on('contextmenu', () => {
rightclicked += 1
$button.remove()
return false
})
cy.on('fail', (err) => {
expect(rightclicked).to.eq(1)
expect(err.message).to.include('`cy.rightclick()` failed because this element')
done()
})
cy.get('button:first').rightclick().rightclick()
})
it('logs once when not dom subject', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
@@ -336,7 +336,7 @@ describe('src/cy/commands/actions/focus', () => {
cy.on('fail', (err) => {
expect(focused).to.eq(1)
expect(err.message).to.include('`cy.focus()` failed because this element')
expect(err.message).to.include('`cy.focus()` failed because the page updated')
done()
})
@@ -791,7 +791,7 @@ describe('src/cy/commands/actions/focus', () => {
cy.on('fail', (err) => {
expect(blurred).to.eq(1)
expect(err.message).to.include('`cy.blur()` failed because this element')
expect(err.message).to.include('`cy.blur()` failed because the page')
expect(err.docsUrl).to.include('https://on.cypress.io/element-has-detached-from-dom')
done()
@@ -323,7 +323,7 @@ describe('src/cy/commands/actions/scroll', () => {
})
it('retries until element is scrollable', () => {
const $container = cy.$$('#nonscroll-becomes-scrollable')
let $container = cy.$$('#nonscroll-becomes-scrollable')
expect($container.get(0).scrollTop).to.eq(0)
expect($container.get(0).scrollLeft).to.eq(0)
@@ -331,6 +331,11 @@ describe('src/cy/commands/actions/scroll', () => {
let retried = false
cy.on('command:retry', _.after(2, () => {
// Replacing the element with itself to ensure that .scrollTo() is requerying the DOM
// as necessary
$container.replaceWith($container[0].outerHTML)
$container = cy.$$('#nonscroll-becomes-scrollable')
$container.css('overflow', 'scroll')
retried = true
}))
@@ -347,10 +352,10 @@ describe('src/cy/commands/actions/scroll', () => {
const scrollTo = cy.spy($.fn, 'scrollTo')
cy.get('button:first').scrollTo('bottom', { ensureScrollable: false }).then(() => {
cy.stub(cy, 'ensureScrollability')
cy.stub(Cypress.ensure, 'isScrollable')
expect(scrollTo).to.be.calledWithMatch({}, { ensureScrollable: false })
expect(cy.ensureScrollability).not.to.be.called
expect(Cypress.ensure.isScrollable).not.to.be.called
})
})
})
@@ -385,17 +390,17 @@ describe('src/cy/commands/actions/scroll', () => {
})
it('waits until the subject is scrollable', () => {
cy.stub(cy, 'ensureScrollability')
cy.stub(Cypress.ensure, 'isScrollable')
.onFirstCall().throws(new Error())
cy.on('command:retry', () => {
return cy.ensureScrollability.returns()
return Cypress.ensure.isScrollable.returns()
})
cy
.get('#scroll-into-view-horizontal')
.scrollTo('right').then(() => {
expect(cy.ensureScrollability).to.be.calledTwice
expect(Cypress.ensure.isScrollable).to.be.calledTwice
})
})
})
@@ -430,7 +435,7 @@ describe('src/cy/commands/actions/scroll', () => {
context('subject errors', () => {
it('throws when not passed DOM element as subject', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.scrollTo()` failed because it requires a DOM element.')
expect(err.message).to.include('`cy.scrollTo()` failed because it requires a DOM element or window.')
expect(err.message).to.include('{foo: bar}')
expect(err.message).to.include('> `cy.noop()`')
@@ -450,6 +455,17 @@ describe('src/cy/commands/actions/scroll', () => {
cy.get('button').scrollTo('500px')
})
it('throws if subject disappears while waiting for scrollability', (done) => {
cy.on('command:retry', () => cy.$$('#nonscroll-becomes-scrollable').remove())
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.scrollTo()` failed because the page updated')
done()
})
cy.get('#nonscroll-becomes-scrollable').scrollTo(500, 300)
})
})
context('argument errors', () => {
@@ -214,11 +214,13 @@ describe('src/cy/commands/actions/select', () => {
const select = cy.$$('select[name=disabled]')
cy.on('command:retry', _.once(() => {
select.prop('disabled', false)
// Replace the element with a copy of itself, to ensure .select() is requerying the DOM
select.replaceWith(select[0].outerHTML)
cy.$$('select[name=disabled]').prop('disabled', false)
}))
cy.get('select[name=disabled]').select('foo')
.invoke('val').should('eq', 'foo')
cy.get('select[name=disabled]').invoke('val').should('eq', 'foo')
})
it('retries until <optgroup> is no longer disabled', () => {
@@ -376,7 +378,7 @@ describe('src/cy/commands/actions/select', () => {
cy.on('fail', (err) => {
expect(selected).to.eq(1)
expect(err.message).to.include('`cy.select()` failed because this element')
expect(err.message).to.include('`cy.select()` failed because the page updated')
done()
})
@@ -543,8 +545,7 @@ describe('src/cy/commands/actions/select', () => {
it('throws when the <select> itself is disabled', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.select()` failed because this element is currently disabled:')
expect(err.docsUrl).to.eq('https://on.cypress.io/select')
expect(err.message).to.include('`cy.select()` failed because this element is `disabled`:')
done()
})
@@ -554,8 +555,7 @@ describe('src/cy/commands/actions/select', () => {
it('throws when the <select> is disabled by a disabled <fieldset>', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.select()` failed because this element is currently disabled:')
expect(err.docsUrl).to.eq('https://on.cypress.io/select')
expect(err.message).to.include('`cy.select()` failed because this element is `disabled`:')
done()
})
@@ -648,7 +648,7 @@ describe('src/cy/commands/actions/select', () => {
cy.get('#select-maps').select('de_dust2').then(function ($select) {
const { lastLog } = this
expect(lastLog.get('$el')).to.eq($select)
expect(lastLog.get('$el')).to.eql($select)
})
})
@@ -336,7 +336,7 @@ describe('src/cy/commands/actions/selectFile', () => {
})
describe('errors', {
defaultCommandTimeout: 500,
defaultCommandTimeout: 100,
}, () => {
it('is a child command', (done) => {
cy.on('fail', (err) => {
@@ -358,17 +358,17 @@ describe('src/cy/commands/actions/selectFile', () => {
it('throws when non-input subject', function (done) {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing one. Your subject is: `<body>...</body>`')
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing a file input, but received the element:')
expect(err.docsUrl).to.eq('https://on.cypress.io/selectfile')
done()
})
cy.get('body').selectFile({ contents: '@foo' })
cy.get('body').selectFile({ contents: '@foo' }, { timeout: 0 })
})
it('throws when non-file input', function (done) {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing one. Your subject is: `<input type="text" id="text-input">`')
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing a file input, but received the element:')
expect(err.docsUrl).to.eq('https://on.cypress.io/selectfile')
done()
})
@@ -378,7 +378,7 @@ describe('src/cy/commands/actions/selectFile', () => {
it('throws when label for non-file input', function (done) {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing one. Your subject is: `<label for="text-input" id="text-label">Text label</label>`')
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing a file input, but received the element:')
expect(err.docsUrl).to.eq('https://on.cypress.io/selectfile')
done()
})
@@ -390,7 +390,7 @@ describe('src/cy/commands/actions/selectFile', () => {
// Even though this label contains a file input, testing on real browsers confirms that clicking it
// does *not* activate the contained input.
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing one. Your subject is: `<label for="nonexistent" id="nonexistent-label">...</label>`')
expect(err.message).to.include('`cy.selectFile()` can only be called on an `<input type="file">` or a `<label for="fileInput">` pointing to or containing a file input, but received the element:')
expect(err.docsUrl).to.eq('https://on.cypress.io/selectfile')
done()
})
@@ -511,6 +511,32 @@ describe('src/cy/commands/actions/selectFile', () => {
})
})
it('retries until label is not disabled', () => {
cy.on('command:retry', () => {
// Replace the label with a copy of itself, to ensure selectFile is requerying the DOM
const hidden = cy.$$('#hidden-basic-label')
hidden.replaceWith(hidden[0].outerHTML)
cy.$$('#hidden-basic-label').show()
})
cy.get('#hidden-basic-label').selectFile({ contents: '@foo' })
})
it('retries until input is not disabled', () => {
cy.on('command:retry', () => {
// Replace the input with a copy of itself, to ensure selectFile is requerying the DOM
const disabled = cy.$$('#disabled')
disabled.replaceWith(disabled[0].outerHTML)
cy.$$('#disabled').attr('disabled', false)
})
cy.get('#disabled-label').selectFile({ contents: '@foo' })
})
/*
* The tests around actionability are somewhat limited, since the functionality is thoroughly tested in the
* `cy.trigger()` unit tests. We include a few tests directly on `cy.selectFile()` in order to ensure we're
@@ -628,25 +654,6 @@ is being covered by another element:
})
})
it('waits until input stops animating', {
defaultCommandTimeout: 1000,
}, () => {
let retries = 0
cy.on('command:retry', (obj) => {
retries += 1
})
cy.stub(cy, 'ensureElementIsNotAnimating')
.throws(new Error('animating!'))
.onThirdCall().returns()
cy.get('#basic').selectFile({ contents: '@foo' }).then(() => {
expect(retries).to.eq(3)
expect(cy.ensureElementIsNotAnimating).to.be.calledThrice
})
})
// TODO(webkit): fix+unskip for experimental webkit
it('can specify scrollBehavior in options', { browser: '!webkit' }, () => {
cy.get('#scroll').then((el) => {
@@ -255,7 +255,7 @@ describe('src/cy/commands/actions/submit', () => {
cy.on('fail', (err) => {
expect(submitted).to.eq(1)
expect(err.message).to.include('`cy.submit()` failed because this element')
expect(err.message).to.include('`cy.submit()` failed because the page')
done()
})
@@ -149,6 +149,18 @@ describe('src/cy/commands/actions/trigger', () => {
})
})
it('requeries the dom while waiting for actionability', () => {
const $input = cy.$$('input:first').attr('disabled', true)
cy.on('command:retry', () => {
// Replace the input with a copy of itself, to ensure trigger is requerying the DOM
$input.replaceWith($input[0].outerHTML)
cy.$$('input:first').attr('disabled', false)
})
cy.get('input:first').trigger('keydown')
})
it('can trigger events on the window', () => {
let expected = false
@@ -224,6 +236,15 @@ describe('src/cy/commands/actions/trigger', () => {
})
describe('actionability', () => {
let retries = 0
beforeEach(() => {
retries = 0
cy.on('command:retry', () => {
retries += 1
})
})
it('can trigger on elements which are hidden until scrolled within parent container', () => {
cy.get('#overflow-auto-container').contains('quux').trigger('mousedown')
})
@@ -268,24 +289,19 @@ describe('src/cy/commands/actions/trigger', () => {
$('<span>span on button</span>').css({ position: 'absolute', left: $btn.offset().left, top: $btn.offset().top, padding: 5, display: 'inline-block', backgroundColor: 'yellow' }).prependTo(cy.$$('body'))
const scrolled = []
let retried = false
let tapped = false
cy.on('scrolled', ($el, type) => {
scrolled.push(type)
})
cy.on('command:retry', ($el, type) => {
retried = true
})
$btn.on('tap', () => {
tapped = true
})
cy.get('#button-covered-in-span').trigger('tap', { force: true }).then(() => {
expect(scrolled).to.be.empty
expect(retried).to.be.false
expect(retries).to.eq(0)
expect(tapped).to.be.true
})
})
@@ -306,7 +322,6 @@ describe('src/cy/commands/actions/trigger', () => {
.prependTo(cy.$$('body'))
const scrolled = []
let retried = false
cy.on('scrolled', ($el, type) => {
return scrolled.push(type)
@@ -314,11 +329,10 @@ describe('src/cy/commands/actions/trigger', () => {
cy.on('command:retry', _.after(3, () => {
$span.hide()
retried = true
}))
cy.get('#button-covered-in-span').trigger('mousedown').then(() => {
expect(retried).to.be.true
expect(retries).to.be.gt(1)
// - element scrollIntoView
// - element scrollIntoView (retry animation coords)
@@ -517,15 +531,12 @@ describe('src/cy/commands/actions/trigger', () => {
it('waits until element becomes visible', () => {
const $btn = cy.$$('#button').hide()
let retried = false
cy.on('command:retry', _.after(3, () => {
$btn.show()
retried = true
}))
cy.get('#button').trigger('mouseover').then(() => {
expect(retried).to.be.true
expect(retries).to.be.gt(1)
})
})
@@ -551,66 +562,44 @@ describe('src/cy/commands/actions/trigger', () => {
})
it('waits until element stops animating', () => {
let retries = 0
cy.on('command:retry', (obj) => {
retries += 1
})
cy.stub(cy, 'ensureElementIsNotAnimating')
.throws(new Error('animating!'))
.onThirdCall().returns()
cy.get('button:first').trigger('mouseover').then(() => {
// - retry animation coords
// - retry animation
// - retry animation
expect(retries).to.eq(3)
expect(cy.ensureElementIsNotAnimating).to.be.calledThrice
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).trigger('mouseover').then(() => {
expect(retries).to.be.gt(1)
})
})
it('does not throw when waiting for animations is disabled', {
it('does not wait when waiting for animations is disabled', {
waitForAnimations: false,
}, () => {
cy.stub(cy, 'ensureElementIsNotAnimating').throws(new Error('animating!'))
cy.get('button:first').trigger('mouseover').then(() => {
expect(cy.ensureElementIsNotAnimating).not.to.be.called
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).trigger('mouseover').then(() => {
expect(retries).to.eq(0)
})
})
it('does not throw when turning off waitForAnimations in options', () => {
cy.stub(cy, 'ensureElementIsNotAnimating').throws(new Error('animating!'))
cy.get('button:first').trigger('tap', { waitForAnimations: false }).then(() => {
expect(cy.ensureElementIsNotAnimating).not.to.be.called
it('does not wait when turning off waitForAnimations in options', () => {
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).trigger('tap', { waitForAnimations: false }).then(() => {
expect(retries).to.eq(0)
})
})
it('passes options.animationDistanceThreshold to cy.ensureElementIsNotAnimating', () => {
cy.spy(cy, 'ensureElementIsNotAnimating')
cy.get('button:first').trigger('tap', { animationDistanceThreshold: 1000 }).then(($btn) => {
const { fromElWindow } = Cypress.dom.getElementCoordinatesByPosition($btn)
const { args } = cy.ensureElementIsNotAnimating.firstCall
expect(args[1]).to.deep.eq([fromElWindow, fromElWindow])
expect(args[2]).to.eq(1000)
it('passes options.animationDistanceThreshold to ensureElementIsNotAnimating', () => {
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).trigger('tap', { animationDistanceThreshold: 1000 }).then(($btn) => {
// One retry, because $actionability always waits for two sets of points to determine if an element is animating.
expect(retries).to.eq(1)
})
})
it('passes config.animationDistanceThreshold to cy.ensureElementIsNotAnimating', () => {
const animationDistanceThreshold = Cypress.config('animationDistanceThreshold')
it('passes config.animationDistanceThreshold to ensureElementIsNotAnimating', () => {
let old = Cypress.config('animationDistanceThreshold')
cy.spy(cy, 'ensureElementIsNotAnimating')
Cypress.config('animationDistanceThreshold', 1000)
cy.get('button:first').trigger('mouseover').then(($btn) => {
const { fromElWindow } = Cypress.dom.getElementCoordinatesByPosition($btn)
const { args } = cy.ensureElementIsNotAnimating.firstCall
expect(args[1]).to.deep.eq([fromElWindow, fromElWindow])
expect(args[2]).to.eq(animationDistanceThreshold)
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).trigger('mouseover').then(($btn) => {
// One retry, because $actionability always waits for two sets of points to determine if an element is animating.
try {
expect(retries).to.eq(1)
} finally {
Cypress.config('animationDistanceThreshold', old)
}
})
})
@@ -1057,7 +1046,7 @@ describe('src/cy/commands/actions/trigger', () => {
cy.on('fail', (err) => {
expect(mouseover).to.eq(1)
expect(err.message).to.include('`cy.trigger()` failed because this element')
expect(err.message).to.include('`cy.trigger()` failed because the page')
done()
})
@@ -93,6 +93,10 @@ describe('src/cy/commands/actions/type - #type', () => {
cy.get('input:text:first').type('bar')
})
it('can type into an input when given a wrapper element', () => {
cy.get('#focus div span').type('foo')
})
it('lists the input as the focused element', () => {
const $input = cy.$$('input:text:first')
@@ -200,6 +204,15 @@ describe('src/cy/commands/actions/type - #type', () => {
})
describe('actionability', () => {
let retries = 0
beforeEach(() => {
retries = 0
cy.on('command:retry', () => {
retries += 1
})
})
it('can forcibly type + click even when element is invisible', () => {
const $txt = cy.$$(':text:first').hide()
@@ -261,35 +274,32 @@ describe('src/cy/commands/actions/type - #type', () => {
it('waits until element becomes visible', () => {
const $txt = cy.$$(':text:first').hide()
const retried = cy.stub()
cy.on('command:retry', _.after(3, () => {
$txt.show()
retried()
// Replace the element with a copy of itself, to ensure that .type() requeries the DOM
// while retrying actionability
$txt.replaceWith($txt[0].innerHTML)
cy.$$(':text:first').show()
}))
cy.get(':text:first').type('foo').then(() => {
expect(retried).to.be.called
expect(retries).to.be.gt(1)
})
})
it('waits until element is no longer disabled', () => {
const $txt = cy.$$(':text:first').prop('disabled', true)
const retried = cy.stub()
const clicked = cy.stub()
$txt.on('click', clicked)
cy.on('command:retry', _.after(3, () => {
$txt.prop('disabled', false)
retried()
}))
cy.get(':text:first').type('foo').then(() => {
expect(clicked).to.be.calledOnce
expect(retried).to.be.called
expect(retries).to.be.gt(1)
})
})
@@ -314,67 +324,44 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('waits until element stops animating', () => {
const retried = cy.stub()
cy.on('command:retry', retried)
cy.stub(cy, 'ensureElementIsNotAnimating')
.throws(new Error('animating!'))
.onThirdCall().returns()
cy.get(':text:first').type('foo').then(() => {
// - retry animation coords
// - retry animation
// - retry animation
expect(retried).to.be.calledThrice
expect(cy.ensureElementIsNotAnimating).to.be.calledThrice
cy.get('button:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).type('foo').then(() => {
expect(retries).to.be.gt(1)
})
})
it('does not throw when waiting for animations is disabled', {
it('does not wait when waiting for animations is disabled', {
waitForAnimations: false,
}, () => {
cy.stub(cy, 'ensureElementIsNotAnimating').throws(new Error('animating!'))
cy.get(':text:first').type('foo').then(() => {
expect(cy.ensureElementIsNotAnimating).not.to.be.called
cy.get(':text:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).type('foo').then(() => {
expect(retries).to.eq(0)
})
})
it('does not throw when turning off waitForAnimations in options', () => {
cy.stub(cy, 'ensureElementIsNotAnimating').throws(new Error('animating!'))
cy.get(':text:first').type('foo', { waitForAnimations: false }).then(() => {
expect(cy.ensureElementIsNotAnimating).not.to.be.called
it('does not wait when turning off waitForAnimations in options', () => {
cy.get(':text:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).type('foo', { waitForAnimations: false }).then(() => {
expect(retries).to.eq(0)
})
})
it('passes options.animationDistanceThreshold to cy.ensureElementIsNotAnimating', () => {
cy.spy(cy, 'ensureElementIsNotAnimating')
cy.get(':text:first').type('foo', { animationDistanceThreshold: 1000 }).then(($txt) => {
const { fromElWindow } = Cypress.dom.getElementCoordinatesByPosition($txt)
const { args } = cy.ensureElementIsNotAnimating.firstCall
expect(args[1]).to.deep.eq([fromElWindow, fromElWindow])
expect(args[2]).to.eq(1000)
it('passes options.animationDistanceThreshold to ensureElementIsNotAnimating', () => {
cy.get(':text:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).type('foo', { animationDistanceThreshold: 1000 }).then(($txt) => {
// One retry, because $actionability always waits for two sets of points to determine if an element is animating.
expect(retries).to.eq(1)
})
})
it('passes config.animationDistanceThreshold to cy.ensureElementIsNotAnimating', () => {
const animationDistanceThreshold = Cypress.config('animationDistanceThreshold')
it('passes config.animationDistanceThreshold to ensureElementIsNotAnimating', () => {
let old = Cypress.config('animationDistanceThreshold')
cy.spy(cy, 'ensureElementIsNotAnimating')
Cypress.config('animationDistanceThreshold', 1000)
cy.get(':text:first').type('foo').then(($txt) => {
const { fromElWindow } = Cypress.dom.getElementCoordinatesByPosition($txt)
const { args } = cy.ensureElementIsNotAnimating.firstCall
expect(args[1]).to.deep.eq([fromElWindow, fromElWindow])
expect(args[2]).to.eq(animationDistanceThreshold)
cy.get(':text:first').then(($btn) => $btn.animate({ width: '30em' }, 100)).type('foo').then(($txt) => {
// One retry, because $actionability always waits for two sets of points to determine if an element is animating.
try {
expect(retries).to.eq(1)
} finally {
Cypress.config('animationDistanceThreshold', old)
}
})
})
@@ -924,7 +911,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('inserts text after existing text input by invoking val', () => {
cy.get('#input-without-value').invoke('val', 'foo').type(' bar').then(($text) => {
cy.get('#input-without-value').invoke('val', 'foo')
cy.get('#input-without-value').type(' bar').then(($text) => {
expect($text).to.have.value('foo bar')
})
})
@@ -1224,7 +1212,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('inserts text after existing text input by invoking val', () => {
cy.get('#number-without-value').invoke('val', '12').type('34').then(($text) => {
cy.get('#number-without-value').invoke('val', '12')
cy.get('#number-without-value').type('34').then(($text) => {
expect($text).to.have.value('1234')
})
})
@@ -1344,7 +1333,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('inserts text after existing text input by invoking val', () => {
cy.get('#email-without-value').invoke('val', 'brian@foo.c').type('om').then(($text) => {
cy.get('#email-without-value').invoke('val', 'brian@foo.c')
cy.get('#email-without-value').type('om').then(($text) => {
expect($text).to.have.value('brian@foo.com')
})
})
@@ -1384,7 +1374,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('inserts text after existing text input by invoking val', () => {
cy.get('#password-without-value').invoke('val', 'secr').type('et').then(($text) => {
cy.get('#password-without-value').invoke('val', 'secr')
cy.get('#password-without-value').type('et').then(($text) => {
expect($text).to.have.value('secret')
})
})
@@ -1441,7 +1432,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('overwrites existing value input by invoking val', () => {
cy.get('#date-without-value').invoke('val', '2016-01-01').type('1959-09-13').then(($text) => {
cy.get('#date-without-value').invoke('val', '2016-01-01')
cy.get('#date-without-value').type('1959-09-13').then(($text) => {
expect($text).to.have.value('1959-09-13')
})
})
@@ -1531,7 +1523,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('overwrites existing value input by invoking val', () => {
cy.get('[type="datetime-local"]').invoke('val', '2016-01-01T05:05').type('1959-09-13T10:10').should('have.value', '1959-09-13T10:10')
cy.get('[type="datetime-local"]').invoke('val', '2016-01-01T05:05')
cy.get('[type="datetime-local"]').type('1959-09-13T10:10').should('have.value', '1959-09-13T10:10')
})
it('errors when invalid datetime', (done) => {
@@ -1559,7 +1552,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('overwrites existing value input by invoking val', () => {
cy.get('#month-without-value').invoke('val', '2016-01').type('1959-09').then(($text) => {
cy.get('#month-without-value').invoke('val', '2016-01')
cy.get('#month-without-value').type('1959-09').then(($text) => {
expect($text).to.have.value('1959-09')
})
})
@@ -1579,7 +1573,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('overwrites existing value input by invoking val', () => {
cy.get('#week-without-value').invoke('val', '2016-W01').type('1959-W09').then(($text) => {
cy.get('#week-without-value').invoke('val', '2016-W01')
cy.get('#week-without-value').type('1959-W09').then(($text) => {
expect($text).to.have.value('1959-W09')
})
})
@@ -1599,7 +1594,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('overwrites existing value input by invoking val', () => {
cy.get('#time-without-value').invoke('val', '01:23:45').type('12:34:56').then(($text) => {
cy.get('#time-without-value').invoke('val', '01:23:45')
cy.get('#time-without-value').type('12:34:56').then(($text) => {
expect($text).to.have.value('12:34:56')
})
})
@@ -1643,7 +1639,8 @@ describe('src/cy/commands/actions/type - #type', () => {
})
it('inserts text after existing text', () => {
cy.get('#input-types [contenteditable]').invoke('text', 'foo').type(' bar').then(($text) => {
cy.get('#input-types [contenteditable]').invoke('text', 'foo')
cy.get('#input-types [contenteditable]').type(' bar').then(($text) => {
expect($text).to.have.text('foo bar')
})
})
@@ -1668,9 +1665,8 @@ describe('src/cy/commands/actions/type - #type', () => {
attachKeyListeners({ ce })
cy.get('#input-types [contenteditable]')
.invoke('text', 'foo')
.type('{enter}')
cy.get('#input-types [contenteditable]').invoke('text', 'foo')
cy.get('#input-types [contenteditable]').type('{enter}')
.should(($text) => {
expect(trimInnerText($text)).eq('foo')
})
@@ -2488,7 +2484,8 @@ describe('src/cy/commands/actions/type - #type', () => {
describe('case-insensitivity', () => {
it('special chars are case-insensitive', () => {
cy.get(':text:first').invoke('val', 'bar').type('{leftarrow}{DeL}').then(($input) => {
cy.get(':text:first').invoke('val', 'bar')
cy.get(':text:first').type('{leftarrow}{DeL}').then(($input) => {
expect($input).to.have.value('ba')
})
})
@@ -2787,9 +2784,8 @@ describe('src/cy/commands/actions/type - #type', () => {
// even if actual and expected appear the same.
const expected = '{\n foo: 1\n bar: 2\n baz: 3\n}'
cy.get('[contenteditable]:first')
.invoke('html', '<div><br></div>')
.type('{{}{enter} foo: 1{enter} bar: 2{enter} baz: 3{enter}}')
cy.get('[contenteditable]:first').invoke('html', '<div><br></div>')
cy.get('[contenteditable]:first').type('{{}{enter} foo: 1{enter} bar: 2{enter} baz: 3{enter}}')
.should(($el) => {
expectMatchInnerText($el, expected)
})
@@ -40,7 +40,7 @@ describe('src/cy/commands/actions/type - #type errors', () => {
cy.on('fail', (err) => {
expect(typed).to.be.calledOnce
expect(err.message).to.include('`cy.type()` failed because this element')
expect(err.message).to.include('`cy.type()` failed because the page')
done()
})
@@ -249,6 +249,8 @@ If you want to skip parsing special character sequences and type the text exactl
it('can type into input with invalid type attribute', () => {
cy.get(':text:first')
.invoke('attr', 'type', 'asdf')
cy.get(':text:first')
.type('foobar')
.should('have.value', 'foobar')
})
@@ -323,10 +323,6 @@ describe('src/cy/commands/agents', () => {
expect(cy.state('aliases').myStub).to.exist
})
it('stores the agent as the subject', function () {
expect(cy.state('aliases').myStub.subject).to.eq(this.stub)
})
it('assigns subject to runnable ctx', function () {
expect(this.myStub).to.eq(this.stub)
})
@@ -404,10 +400,6 @@ describe('src/cy/commands/agents', () => {
expect(cy.state('aliases')['my.stub']).to.exist
})
it('stores the agent as the subject', function () {
expect(cy.state('aliases')['my.stub'].subject).to.eq(this.stub)
})
it('assigns subject to runnable ctx', function () {
expect(this['my.stub']).to.eq(this.stub)
})
@@ -1,5 +1,5 @@
const { assertLogLength } = require('../../support/utils')
const { _, $ } = Cypress
const { _ } = Cypress
describe('src/cy/commands/aliasing', () => {
beforeEach(() => {
@@ -7,14 +7,6 @@ describe('src/cy/commands/aliasing', () => {
})
context('#as', () => {
it('is special utility command', () => {
cy.wrap('foo').as('f').then(() => {
const cmd = cy.queue.find({ name: 'as' })
expect(cmd.get('type')).to.eq('utility')
})
})
it('does not change the subject', () => {
const body = cy.$$('body')
@@ -34,11 +26,13 @@ describe('src/cy/commands/aliasing', () => {
cy.get('@body')
})
it('stores the resulting subject as the alias', () => {
const $body = cy.$$('body')
it('stores the resulting subject chain as the alias', () => {
cy.get('body').as('b').then(() => {
expect(cy.state('aliases').b.subject.get(0)).to.eq($body.get(0))
const { subjectChain } = cy.state('aliases').b
expect(subjectChain.length).to.eql(2)
expect(subjectChain[0]).to.be.undefined
expect(subjectChain[1].commandName).to.eq('get')
})
})
@@ -50,6 +44,15 @@ describe('src/cy/commands/aliasing', () => {
})
})
it('retries previous commands invoked inside custom commands', () => {
Cypress.Commands.add('get2', (selector) => cy.get(selector))
cy.get2('body').children('div').as('divs')
cy.visit('/fixtures/dom.html')
cy.get('@divs')
})
it('retries primitives and assertions', () => {
const obj = {}
@@ -87,29 +90,13 @@ describe('src/cy/commands/aliasing', () => {
})
})
context('DOM subjects', () => {
it('assigns the remote jquery instance', () => {
const obj = {}
it('retries previous commands invoked inside custom commands', () => {
Cypress.Commands.add('get2', (selector) => cy.get(selector))
const jquery = () => {
return obj
}
cy.get2('body').children('div').as('divs')
cy.visit('/fixtures/dom.html')
cy.state('jQuery', jquery)
cy.get('input:first').as('input').then(function () {
expect(this.input).to.eq(obj)
})
})
it('retries previous commands invoked inside custom commands', () => {
Cypress.Commands.add('get2', (selector) => cy.get(selector))
cy.get2('body').children('div').as('divs')
cy.visit('/fixtures/dom.html')
cy.get('@divs')
})
cy.get('@divs')
})
context('#assign', () => {
@@ -305,8 +292,7 @@ describe('src/cy/commands/aliasing', () => {
it('does not match alias when the alias has already been applied', () => {
cy
.visit('/fixtures/commands.html')
.server()
.route(/foo/, {}).as('getFoo')
.intercept(/foo/, {}).as('getFoo')
.then(function () {
// 1 log from visit
// 1 log from route
@@ -328,9 +314,8 @@ describe('src/cy/commands/aliasing', () => {
// sanity check without command overwrite
cy.wrap('alias value').as('myAlias')
.then(() => {
expect(cy.getAlias('@myAlias'), 'alias exists').to.exist
expect(cy.getAlias('@myAlias'), 'alias value')
.to.have.property('subject', 'alias value')
expect(cy.getAlias('@myAlias')).to.exist
expect(cy.getAlias('@myAlias').subjectChain).to.eql(['alias value'])
})
.then(() => {
// cy.get returns the alias
@@ -349,10 +334,9 @@ describe('src/cy/commands/aliasing', () => {
cy.wrap('alias value').as('myAlias')
.then(() => {
expect(wrapCalled, 'overwrite was called').to.be.true
expect(cy.getAlias('@myAlias'), 'alias exists').to.exist
expect(cy.getAlias('@myAlias'), 'alias value')
.to.have.property('subject', 'alias value')
expect(wrapCalled).to.be.true
expect(cy.getAlias('@myAlias')).to.exist
expect(cy.getAlias('@myAlias').subjectChain).to.eql(['alias value'])
})
.then(() => {
// verify cy.get works in arrow function
@@ -382,9 +366,8 @@ describe('src/cy/commands/aliasing', () => {
.then(() => {
expect(wrapCalled, 'overwrite was called').to.be.true
expect(thenCalled, 'then was called').to.be.true
expect(cy.getAlias('@myAlias'), 'alias exists').to.exist
expect(cy.getAlias('@myAlias'), 'alias value')
.to.have.property('subject', 'alias value')
expect(cy.getAlias('@myAlias')).to.exist
expect(cy.getAlias('@myAlias').subjectChain).to.eql(['alias value'])
})
.then(() => {
// verify cy.get works in arrow function
@@ -400,9 +383,9 @@ describe('src/cy/commands/aliasing', () => {
// sanity test before the next one
cy.wrap(1).as('myAlias')
cy.wrap(2).then(function (subj) {
expect(subj, 'subject').to.equal(2)
expect(this, 'this is defined').to.not.be.undefined
expect(this.myAlias, 'this has the alias as a property').to.eq(1)
expect(subj).to.equal(2)
expect(this).to.not.be.undefined
expect(this.myAlias).to.eq(1)
})
})
@@ -414,8 +397,8 @@ describe('src/cy/commands/aliasing', () => {
cy.wrap(1).as('myAlias')
cy.wrap(2).then(function (subj) {
expect(subj, 'subject').to.equal(2)
expect(this, 'this is defined').to.not.be.undefined
expect(subj).to.equal(2)
expect(this).to.not.be.undefined
expect(this.myAlias).to.eq(1)
})
})
@@ -428,166 +411,61 @@ describe('src/cy/commands/aliasing', () => {
cy.wrap(1).as('myAlias')
cy.wrap(2).then(function (subj) {
expect(subj, 'subject').to.equal(2)
expect(this, 'this is defined').to.not.be.undefined
expect(subj).to.equal(2)
expect(this).to.not.be.undefined
expect(this.myAlias).to.eq(1)
})
})
})
})
context('#replayCommandsFrom', () => {
describe('subject in document', () => {
it('returns if subject is still in the document', () => {
cy.get('#list').as('list').then(() => {
const currentLength = cy.queue.length
cy.get('@list').then(() => {
// should only add the .get() and the .then()
expect(cy.queue.length).to.eq(currentLength + 2)
})
context('#replaying subjects', () => {
it('returns if subject is still in the document', () => {
cy.get('#list').as('list').then((firstList) => {
cy.get('@list').then((secondList) => {
expect(firstList).to.eql(secondList)
})
})
})
describe('subject not in document', () => {
it('inserts into the queue', () => {
const existingNames = cy.queue.names()
it('requeries when reading alias', () => {
cy
.get('#list li')
.as('items').then((firstItems) => {
cy.$$('#list').append('<li class="foobar">123456789</li>')
cy
.get('#list li').eq(0).as('firstLi').then(($li) => {
return $li.remove()
})
.get('@firstLi').then(() => {
expect(cy.queue.names()).to.deep.eq(
existingNames.concat(
['get', 'eq', 'as', 'then', 'get', 'get', 'eq', 'then'],
),
)
cy.get('@items').then((secondItems) => {
expect(firstItems).to.have.length(3)
expect(secondItems).to.have.length(4)
})
})
})
it('replays from last root to current', () => {
const first = cy.$$('#list li').eq(0)
const second = cy.$$('#list li').eq(1)
it('requeries when subject is not in the DOM', () => {
cy
.get('#list li')
.as('items').then((firstItems) => {
firstItems.remove()
setTimeout(() => {
cy.$$('#list').append('<li class="foobar">123456789</li>')
}, 50)
cy
.get('#list li').eq(0).as('firstLi').then(($li) => {
expect($li.get(0)).to.eq(first.get(0))
return $li.remove()
})
.get('@firstLi').then(($li) => {
expect($li.get(0)).to.eq(second.get(0))
cy.get('@items').then((secondItems) => {
expect(secondItems).to.have.length(1)
})
})
})
it('replays up until first root command', () => {
const existingNames = cy.queue.names()
it('only retries up to last command', () => {
cy
.get('#list li')
.then((items) => items.length)
.as('itemCount')
.then(() => cy.$$('#list li').remove())
cy
.get('body').noop({})
.get('#list li').eq(0).as('firstLi').then(($li) => {
return $li.remove()
})
.get('@firstLi').then(() => {
expect(cy.queue.names()).to.deep.eq(
existingNames.concat(
['get', 'noop', 'get', 'eq', 'as', 'then', 'get', 'get', 'eq', 'then'],
),
)
})
})
it('resets the chainerId allow subjects to be carried on', () => {
cy.get('#dom').find('#button').as('button').then(($button) => {
$button.remove()
cy.$$('#dom').append($('<button />', { id: 'button' }))
return null
})
// when cy is a separate chainer there *was* a bug
// that cause the subject to null because of different
// chainer id's
cy.get('@button').then(($button) => {
expect($button).to.have.id('button')
})
})
it('skips commands which did not change, and starts at the first valid subject or parent command', () => {
const existingNames = cy.queue.names()
cy.$$('#list li').click(function () {
const ul = $(this).parent()
const lis = ul.children().clone()
// this simulates a re-render
ul.children().remove()
ul.append(lis)
return lis.first().remove()
})
cy
.get('#list li')
.then(($lis) => {
return $lis
})
.as('items')
.first()
.click()
.as('firstItem')
.then(() => {
expect(cy.queue.names()).to.deep.eq(
existingNames.concat(
['get', 'then', 'as', 'first', 'click', 'as', 'then', 'get', 'should', 'then', 'get', 'should', 'then'],
),
)
})
.get('@items')
.should('have.length', 2)
.then(() => {
expect(cy.queue.names()).to.deep.eq(
existingNames.concat(
['get', 'then', 'as', 'first', 'click', 'as', 'then', 'get', 'get', 'should', 'then', 'get', 'should', 'then'],
),
)
})
.get('@firstItem')
.should('contain', 'li 1')
.then(() => {
expect(cy.queue.names()).to.deep.eq(
existingNames.concat(
['get', 'then', 'as', 'first', 'click', 'as', 'then', 'get', 'get', 'should', 'then', 'get', 'get', 'first', 'should', 'then'],
),
)
})
})
it('inserts assertions', (done) => {
const existingNames = cy.queue.names()
cy
.get('#checkboxes input')
.eq(0)
.should('be.checked', 'cockatoo')
.as('firstItem')
.then(($input) => {
return $input.remove()
})
.get('@firstItem')
.then(() => {
expect(cy.queue.names()).to.deep.eq(
existingNames.concat(
['get', 'eq', 'should', 'as', 'then', 'get', 'get', 'eq', 'should', 'then'],
),
)
done()
})
})
// Even though the list items have been removed from the DOM, 'then' can't be retried
// so we just have the primitive value "3" as our subject.
cy.get('@itemCount').should('eq', 3)
})
})
@@ -616,5 +494,14 @@ describe('src/cy/commands/aliasing', () => {
.get('@lastDiv')
})
})
// TODO: Re-enable as part of https://github.com/cypress-io/cypress/issues/23902
it.skip('maintains .within() context while reading aliases', () => {
cy.get('#specific-contains').within(() => {
cy.get('span').as('spanWithin').should('have.length', 1)
})
cy.get('@spanWithin').should('have.length', 1)
})
})
})
@@ -1,332 +0,0 @@
const { assertLogLength } = require('../../support/utils')
const { _, $ } = Cypress
describe('src/cy/commands/angular', () => {
beforeEach(() => {
cy.visit('/fixtures/angular.html')
})
describe('#ng', () => {
context('find by binding', () => {
it('finds color.name binding elements', () => {
const spans = cy.$$('.colors span.name')
cy.ng('binding', 'color.name').then(($spans) => {
$spans.each((i, span) => {
expect(span).to.eq(spans[i])
})
})
})
describe('errors', {
defaultCommandTimeout: 100,
}, () => {
beforeEach(function () {
this.angular = cy.state('window').angular
})
afterEach(function () {
cy.state('window').angular = this.angular
})
it('throws when cannot find angular', { retries: 2 }, (done) => {
delete cy.state('window').angular
cy.on('fail', (err) => {
expect(err.message).to.include('Angular global (`window.angular`) was not found in your window. You cannot use `cy.ng()` methods without angular.')
done()
})
cy.ng('binding', 'phone')
})
it('throws when binding cannot be found', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('Could not find element for binding: \'not-found\'.')
done()
})
cy.ng('binding', 'not-found')
})
it('cancels additional finds when aborted', (done) => {
cy.timeout(1000)
cy.stub(Cypress.runner, 'stop')
let retry = _.after(2, () => {
Cypress.stop()
})
cy.on('command:retry', retry)
cy.on('fail', (err) => {
done(err)
})
cy.on('stop', () => {
retry = cy.spy(cy, 'retry')
_.delay(() => {
expect(retry.callCount).to.eq(0)
done()
}, 100)
})
cy.ng('binding', 'not-found')
})
})
})
context('find by repeater', () => {
const ngPrefixes = { 'phone in phones': 'ng-', 'phone2 in phones': 'ng_', 'phone3 in phones': 'data-ng-', 'phone4 in phones': 'x-ng-' }
_.each(ngPrefixes, (prefix, attr) => {
it(`finds by ${prefix}repeat`, () => {
// make sure we find this element
const li = cy.$$(`[${prefix}repeat*='${attr}']`)
expect(li).to.exist
// and make sure they are the same DOM element
cy.ng('repeater', attr).then(($li) => {
expect($li.get(0)).to.eq(li.get(0))
})
})
})
it('favors earlier items in the array when duplicates are found', () => {
const li = cy.$$('[ng-repeat*=\'foo in foos\']')
cy.ng('repeater', 'foo in foos').then(($li) => {
expect($li.get(0)).to.eq(li.get(0))
})
})
it('waits to find a missing input', () => {
const missingLi = $('<li />', { 'data-ng-repeat': 'li in lis' })
// wait until we're ALMOST about to time out before
// appending the missingInput
cy.on('command:retry', _.after(2, () => {
cy.$$('body').append(missingLi)
}))
cy.ng('repeater', 'li in lis').then(($li) => {
expect($li).to.match(missingLi)
})
})
describe('errors', {
defaultCommandTimeout: 100,
}, () => {
beforeEach(function () {
this.angular = cy.state('window').angular
})
afterEach(function () {
cy.state('window').angular = this.angular
})
it('throws when repeater cannot be found', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('Could not find element for repeater: \'not-found\'. Searched [ng-repeat*=\'not-found\'], [ng_repeat*=\'not-found\'], [data-ng-repeat*=\'not-found\'], [x-ng-repeat*=\'not-found\'].')
done()
})
cy.ng('repeater', 'not-found')
})
it('cancels additional finds when aborted', (done) => {
cy.timeout(1000)
cy.stub(Cypress.runner, 'stop')
let retry = _.after(2, () => {
Cypress.stop()
})
cy.on('command:retry', retry)
cy.on('fail', (err) => {
done(err)
})
cy.on('stop', () => {
retry = cy.spy(cy, 'retry')
_.delay(() => {
expect(retry.callCount).to.eq(0)
done()
}, 100)
})
cy.ng('repeater', 'not-found')
})
it('throws when cannot find angular', (done) => {
delete cy.state('window').angular
cy.on('fail', (err) => {
expect(err.message).to.include('Angular global (`window.angular`) was not found in your window. You cannot use `cy.ng()` methods without angular.')
done()
})
cy.ng('repeater', 'phone in phones')
})
})
describe('log', () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
this.lastLog = log
this.logs.push(log)
}
})
return null
})
it('does not incorrectly merge 2nd assertion into 1st', function () {
cy
.ng('repeater', 'foo in foos').should('have.length', 2)
.url().should('include', ':')
.then(() => {
assertLogLength(this.logs, 2)
expect(this.logs[0].get('state')).to.eq('passed')
expect(this.logs[1].get('state')).to.eq('passed')
})
})
})
})
context('find by model', () => {
const ngPrefixes = { query: 'ng-', query2: 'ng_', query3: 'data-ng-', query4: 'x-ng-' }
_.each(ngPrefixes, (prefix, attr) => {
it(`finds element by ${prefix}model`, () => {
// make sure we find this element
const input = cy.$$(`[${prefix}model=${attr}]`)
expect(input).to.exist
// and make sure they are the same DOM element
cy.ng('model', attr).then(($input) => {
expect($input.get(0)).to.eq(input.get(0))
})
})
})
it('favors earlier items in the array when duplicates are found', () => {
const input = cy.$$('[ng-model=foo]')
cy.ng('model', 'foo').then(($input) => {
expect($input.get(0)).to.eq(input.get(0))
})
})
it('waits to find a missing input', () => {
const missingInput = $('<input />', { 'data-ng-model': 'missing-input' })
// wait until we're ALMOST about to time out before
// appending the missingInput
cy.on('command:retry', _.after(2, () => {
cy.$$('body').append(missingInput)
}))
cy.ng('model', 'missing-input').then(($input) => {
expect($input).to.match(missingInput)
})
})
it('cancels other retries when one resolves', () => {
const retry = cy.spy(cy, 'retry')
const missingInput = $('<input />', { 'data-ng-model': 'missing-input' })
cy.on('command:retry', _.after(6, _.once(() => {
cy.$$('body').append(missingInput)
})))
// we want to make sure that the ng promises do not continue
// to retry after the first one resolves
cy.ng('model', 'missing-input')
.then(() => {
return retry.resetHistory()
})
.wait(100)
.then(() => {
expect(retry.callCount).to.eq(0)
})
})
describe('errors', {
defaultCommandTimeout: 100,
}, () => {
beforeEach(function () {
this.angular = cy.state('window').angular
})
afterEach(function () {
cy.state('window').angular = this.angular
})
it('throws when model cannot be found', (done) => {
cy.ng('model', 'not-found')
cy.on('fail', (err) => {
expect(err.message).to.include('Could not find element for model: \'not-found\'. Searched [ng-model=\'not-found\'], [ng_model=\'not-found\'], [data-ng-model=\'not-found\'], [x-ng-model=\'not-found\'].')
done()
})
})
it('cancels additional finds when aborted', (done) => {
cy.timeout(1000)
cy.stub(Cypress.runner, 'stop')
let retry = _.after(2, () => {
Cypress.stop()
})
cy.on('command:retry', retry)
cy.on('fail', (err) => {
done(err)
})
cy.on('stop', () => {
retry = cy.spy(cy, 'retry')
_.delay(() => {
expect(retry.callCount).to.eq(0)
done()
}, 100)
})
cy.ng('model', 'not-found')
})
it('throws when cannot find angular', (done) => {
delete cy.state('window').angular
cy.on('fail', (err) => {
expect(err.message).to.include('Angular global (`window.angular`) was not found in your window. You cannot use `cy.ng()` methods without angular.')
done()
})
cy.ng('model', 'query')
})
})
})
})
})
@@ -134,21 +134,6 @@ describe('src/cy/commands/assertions', () => {
cy.noop(obj).its('requestJSON').should('have.property', 'teamIds').should('deep.eq', [2])
})
// TODO: make cy.then retry
// https://github.com/cypress-io/cypress/issues/627
it.skip('outer assertions retry on cy.then', () => {
const obj = { foo: 'bar' }
cy.wrap(obj).then(() => {
setTimeout(() => {
obj.foo = 'baz'
}
, 1000)
return obj
}).should('deep.eq', { foo: 'baz' })
})
it('does it retry when wrapped', () => {
const obj = { foo: 'bar' }
@@ -325,6 +310,19 @@ describe('src/cy/commands/assertions', () => {
})
})
// https://github.com/cypress-io/cypress/issues/22587
it('does not allow cypress commands inside the callback', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`cy.should()` failed because you invoked a command inside the callback. `cy.should()` retries the inner function, which would result in commands being added to the queue multiple times. Use `cy.then()` instead of `cy.should()`, or move any commands outside the callback function.\n\nThe command invoked was:\n\n > `cy.log()`')
done()
})
cy.window().should((win) => {
cy.log(win)
})
})
context('remote jQuery instances', () => {
beforeEach(function () {
this.remoteWindow = cy.state('window')
@@ -448,13 +446,13 @@ describe('src/cy/commands/assertions', () => {
expect(this.logs[1].get('name')).to.eq('assert')
expect(this.logs[1].get('state')).to.eq('failed')
expect(this.logs[1].get('error').name).to.eq('CypressError')
expect(this.logs[1].get('error').name).to.eq('AssertionError')
expect(this.logs[1].get('error')).to.eq(err)
done()
})
cy.contains('Nested Find').should('have.length', 2)
cy.contains('Nested Find', { timeout: 50 }).should('have.length', 2)
})
// https://github.com/cypress-io/cypress/issues/6384
@@ -651,7 +649,7 @@ describe('src/cy/commands/assertions', () => {
done()
})
cy.get('button:first', { timeout: 100 }).should('have.class', 'does-not-have-class')
cy.get('button:first', { timeout: 500 }).should('have.class', 'does-not-have-class')
})
it('has a pending state while retrying for commands with onFail', (done) => {
@@ -666,29 +664,7 @@ describe('src/cy/commands/assertions', () => {
cy.on('fail', () => {})
cy.readFile('does-not-exist.json').should('exist')
})
it('throws when the subject isnt in the DOM', function (done) {
cy.$$('button:first').click(function () {
$(this).addClass('foo').remove()
})
cy.on('fail', (err) => {
const names = _.invokeMap(this.logs, 'get', 'name')
// the 'should' is not here because based on
// when we check for the element to be detached
// it never actually runs the assertion
expect(names).to.deep.eq(['get', 'click'])
expect(err.message).to.include('`cy.should()` failed because this element is detached')
done()
})
cy.get('button:first').click().should('have.class', 'foo').then(() => {
done('cy.should was supposed to fail')
})
cy.readFile('does-not-exist.json', { timeout: 500 }).should('exist')
})
it('throws when the subject eventually isnt in the DOM', function (done) {
@@ -705,14 +681,12 @@ describe('src/cy/commands/assertions', () => {
// should is present here due to the retry
expect(names).to.deep.eq(['get', 'click', 'assert'])
expect(err.message).to.include('`cy.should()` failed because this element is detached')
expect(err.message).to.include('`cy.should()` failed because the page updated')
done()
})
cy.get('button:first').click().should('have.class', 'foo').then(() => {
done('cy.should was supposed to fail')
})
cy.get('button:first').click().should('have.class', 'foo')
})
it('throws when should(\'have.length\') isnt a number', function (done) {
@@ -838,7 +812,7 @@ describe('src/cy/commands/assertions', () => {
return null
})
it('does not output should logs on failures', function (done) {
it('does not output should logs on failures', { defaultCommandTimeout: 50 }, function (done) {
cy.on('fail', () => {
const { length } = this.logs
@@ -864,23 +838,8 @@ describe('src/cy/commands/assertions', () => {
done()
})
cy.get('body').then(() => {
expect(cy.currentSubject()).to.match('body')
})
})
it('sets type to child when subject matches', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('type')).to.eq('child')
done()
}
})
cy.wrap('foo').then(() => {
expect('foo').to.eq('foo')
cy.get('body').then((subject) => {
expect(subject).to.match('body')
})
})
@@ -1539,7 +1498,7 @@ describe('src/cy/commands/assertions', () => {
it('fails not.visible for detached DOM', function (done) {
cy.on('fail', (err) => {
expect(err.message).include('detached')
expect(err.message).include('`cy.should()` failed because the page updated')
done()
})
@@ -2117,6 +2076,20 @@ describe('src/cy/commands/assertions', () => {
cy.wrap(undefined).should('have.value', 'somevalue')
})
it('shows subject instead of undefined when a previous traversal errors', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('expected **subject** to have class **updated**')
done()
}
})
cy.get('body')
.contains('Does not exist')
.should('have.class', 'updated')
})
})
context('descendants', () => {
@@ -213,8 +213,6 @@ describe('command log', () => {
cy.root(options)
})
// Ignore cy.route() because it doesn't log to reporter.
testOptions('screenshot', { capture: 'viewport' }, 0, (options) => {
cy.screenshot(options)
})
@@ -231,8 +229,6 @@ describe('command log', () => {
cy.get('#fruits').select('apples', options)
})
// Ignore cy.server() because it doesn't log to reporter.
// TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23444
// testOptions('setCookie', { httpOnly: true }, 0, (options) => {
// cy.setCookie('auth_key', '123key', options)
@@ -88,6 +88,21 @@ describe('src/cy/commands/commands', () => {
})
})
it('throws when attempting to add an existing query', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`Cypress.Commands.addQuery()` is used to create new queries, but `get` is an existing Cypress command or query, or is reserved internally by Cypress.\n\n If you want to override an existing command or query, use `Cypress.Commands.overrideQuery()` instead.')
expect(err.docsUrl).to.eq('https://on.cypress.io/custom-queries')
done()
})
Cypress.Commands.addQuery('get', () => {
cy
.get('[contenteditable]')
.first()
})
})
it('allows calling .add with hover / mount', () => {
let calls = 0
@@ -120,6 +135,21 @@ describe('src/cy/commands/commands', () => {
.first()
})
})
it('throws when attempting to add a query with the same name as an internal function', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.eq('`Cypress.Commands.addQuery()` cannot create a new query named `addCommand` because that name is reserved internally by Cypress.')
expect(err.docsUrl).to.eq('https://on.cypress.io/custom-queries')
done()
})
Cypress.Commands.addQuery('addCommand', () => {
cy
.get('[contenteditable]')
.first()
})
})
})
context('errors', () => {
@@ -215,6 +215,19 @@ describe('src/cy/commands/connectors', () => {
})
})
it('completely resets the subject chain when queries are used', () => {
cy.wrap('foo')
.then(() => cy.get('body'))
.then(() => {
// We expect the current subject chain to look like [undefined, get()],
// inherited from .get() inside the first .then().
// There was a regression where it would instead look like ['foo', get()],
// mixing the subjects from .wrap() and .get().
expect(cy.subjectChain()[0]).to.be.undefined
})
})
describe('errors', {
defaultCommandTimeout: 100,
}, () => {
@@ -310,7 +323,7 @@ describe('src/cy/commands/connectors', () => {
return $div
})
.then(function () {
expect(cy.currentSubject()).not.to.be.instanceof(this.remoteWindow.$)
expect(cy.subject()).not.to.be.instanceof(this.remoteWindow.$)
})
})
})
@@ -628,8 +641,8 @@ describe('src/cy/commands/connectors', () => {
}
cy.on('fail', (err) => {
expect(err.message).to.include('Timed out retrying after 100ms: `cy.invoke()` errored because the property: `bar` returned a `string` value instead of a function. `cy.invoke()` can only be used on properties that return callable functions.')
expect(err.message).to.include('`cy.invoke()` waited for the specified property `bar` to return a function, but it never did.')
expect(err.message).to.include('Timed out retrying after 100ms: `cy.invoke()` errored because the property: `foo.bar` returned a `string` value instead of a function. `cy.invoke()` can only be used on properties that return callable functions.')
expect(err.message).to.include('`cy.invoke()` waited for the specified property `foo.bar` to return a function, but it never did.')
expect(err.message).to.include('If you want to assert on the property\'s value, then switch to use `cy.its()` and add an assertion such as:')
expect(err.message).to.include('`cy.wrap({ foo: \'bar\' }).its(\'foo\').should(\'eq\', \'bar\')`')
expect(err.docsUrl).to.eq('https://on.cypress.io/invoke')
@@ -642,7 +655,7 @@ describe('src/cy/commands/connectors', () => {
})
})
describe('accepts a options argument', () => {
describe('accepts an options argument', () => {
it('changes subject to function invocation', () => {
cy.noop({ foo () {
return 'foo'
@@ -729,16 +742,15 @@ describe('src/cy/commands/connectors', () => {
cy.wrap({ foo () {
return 'foo'
} }).invoke(() => {
return {}
})
} })
.invoke(() => {})
})
it('throws when first parameter is neither of type object nor of type string nor of type number', function (done) {
it('throws when we can\'t determine both a valid options and path', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include('`cy.invoke()` only accepts a string or a number as the functionName argument.')
expect(err.message).to.include('`cy.invoke()` only accepts an object as the options argument.')
expect(lastLog.get('error').message).to.include(err.message)
done()
@@ -903,30 +915,19 @@ describe('src/cy/commands/connectors', () => {
Command: 'invoke',
Function: '.bar()',
Subject: this.obj,
'With Arguments': [],
Yielded: 'bar',
})
})
})
it('#consoleProps as a function property with args', function () {
cy.noop(this.obj).invoke('sum', 1, 2, 3).then(function () {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
Command: 'invoke',
Function: '.sum(1, 2, 3)',
'With Arguments': [1, 2, 3],
Subject: this.obj,
Yielded: 6,
})
})
})
it('#consoleProps as a function reduced property with args', function () {
it('#consoleProps as a deep function property with args', function () {
cy.noop(this.obj).invoke('math.sum', 1, 2, 3).then(function () {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
Command: 'invoke',
Function: '.math.sum(1, 2, 3)',
'With Arguments': [1, 2, 3],
Subject: this.obj['math'],
Subject: this.obj,
Yielded: 6,
})
})
@@ -939,8 +940,9 @@ describe('src/cy/commands/connectors', () => {
expect(consoleProps).to.deep.eq({
Command: 'invoke',
Function: '.hide()',
Subject: $btn.get(0),
Yielded: $btn.get(0),
Subject: $btn,
'With Arguments': [],
Yielded: $btn,
})
})
})
@@ -1058,7 +1060,7 @@ describe('src/cy/commands/connectors', () => {
cy.on('fail', (err) => {
const { lastLog } = this
expect(err.message).to.include('Timed out retrying after 100ms: `cy.invoke()` errored because the property: `baz` does not exist on your subject.')
expect(err.message).to.include('Timed out retrying after 100ms: `cy.invoke()` errored because the property: `foo.bar.baz.fizz` does not exist on your subject.')
expect(lastLog.get('error').message).to.include(err.message)
expect(err.docsUrl).to.eq('https://on.cypress.io/invoke')
@@ -1081,10 +1083,8 @@ describe('src/cy/commands/connectors', () => {
this.remoteWindow = cy.state('window')
})
it('proxies to #invokeFn', () => {
const fn = () => {
return 'bar'
}
it('returns function properties', () => {
const fn = () => 'bar'
cy.wrap({ foo: fn }).its('foo').should('eq', fn)
})
@@ -1244,7 +1244,8 @@ describe('src/cy/commands/connectors', () => {
cy.stub()
.onCall(0).returns(undefined)
.onCall(1).returns(undefined)
.onCall(2).returns(true),
.onCall(2).returns(undefined)
.onCall(3).returns(true),
)
cy.wrap(obj).its('foo').should('eq', true)
@@ -1268,15 +1269,6 @@ describe('src/cy/commands/connectors', () => {
cy.wrap({}).its('foo').should('not.exist')
cy.wrap({}).its('foo').should('be.undefined')
cy.wrap({}).its('foo').should('not.be.ok')
// TODO: should these really pass here?
// isn't this the same situation as: cy.should('not.have.class', '...')
//
// when we use the 'eq' and 'not.eq' chainer aren't we effectively
// saying that it must *have* a value as opposed to the property not
// existing at all?
//
// does a tree falling in the forest really make a sound?
cy.wrap({}).its('foo').should('eq', undefined)
cy.wrap({}).its('foo').should('not.eq', 'bar')
})
@@ -1309,71 +1301,6 @@ describe('src/cy/commands/connectors', () => {
cy.wrap(obj).its('foo').should('eq', undefined)
})
describe('accepts a options argument and works as without options argument', () => {
it('proxies to #invokeFn', () => {
const fn = () => {
return 'bar'
}
cy.wrap({ foo: fn }).its('foo', { log: false }).should('eq', fn)
})
it('does not invoke a function and uses as a property', () => {
const fn = () => {
return 'fn'
}
fn.bar = 'bar'
cy.wrap(fn).its('bar', { log: false }).should('eq', 'bar')
})
it('works with numerical indexes', () => {
cy.wrap(['foo', 'bar']).its(1, {}).should('eq', 'bar')
})
describe('.log', () => {
beforeEach(function () {
this.obj = {
foo: 'foo bar baz',
num: 123,
}
cy.on('log:added', (attrs, log) => {
this.lastLog = log
})
return null
})
it('logs obj as a property', function () {
cy.noop(this.obj).its('foo', { log: true }).then(function () {
const obj = {
name: 'its',
message: '.foo',
}
const { lastLog } = this
_.each(obj, (value, key) => {
expect(lastLog.get(key)).to.deep.eq(value)
})
})
})
it('#consoleProps as a regular property', function () {
cy.noop(this.obj).its('num', { log: true }).then(function () {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
Command: 'its',
Property: '.num',
Subject: this.obj,
Yielded: 123,
})
})
})
})
})
describe('.log', () => {
beforeEach(function () {
this.obj = {
@@ -1570,7 +1497,7 @@ describe('src/cy/commands/connectors', () => {
it('throws the traversalErr as precedence when property does not exist even if the additional assertions fail', function (done) {
cy.once('fail', (err) => {
const { itsLog, lastLog } = this
const { itsLog } = this
expect(err.message).to.include('Timed out retrying after 100ms: `cy.its()` errored because the property: `b` does not exist on your subject.')
expect(err.message).to.include('`cy.its()` waited for the specified property `b` to exist, but it never did.')
@@ -1578,13 +1505,8 @@ describe('src/cy/commands/connectors', () => {
expect(err.message).to.include('`cy.wrap({ foo: \'bar\' }).its(\'quux\').should(\'not.exist\')`')
expect(err.docsUrl).to.eq('https://on.cypress.io/its')
expect(itsLog.get('state')).to.eq('passed')
expect(itsLog.get('error')).to.be.undefined
expect(lastLog.get('name')).to.eq('assert')
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('message')).to.contain('to be true')
expect(lastLog.get('error').message).to.include(err.message)
expect(itsLog.get('state')).to.eq('failed')
expect(itsLog.get('error').message).to.include(err.message)
done()
})
@@ -1594,7 +1516,7 @@ describe('src/cy/commands/connectors', () => {
it('throws the traversalErr as precedence when property value is undefined even if the additional assertions fail', function (done) {
cy.once('fail', (err) => {
const { itsLog, lastLog } = this
const { itsLog } = this
expect(err.message).to.include('Timed out retrying after 100ms: `cy.its()` errored because the property: `a` returned a `undefined` value.')
expect(err.message).to.include('`cy.its()` waited for the specified property `a` to become accessible, but it never did.')
@@ -1602,13 +1524,8 @@ describe('src/cy/commands/connectors', () => {
expect(err.message).to.include('`cy.wrap({ foo: undefined }).its(\'foo\').should(\'be.undefined\')`')
expect(err.docsUrl).to.eq('https://on.cypress.io/its')
expect(itsLog.get('state')).to.eq('passed')
expect(itsLog.get('error')).to.be.undefined
expect(lastLog.get('name')).to.eq('assert')
expect(lastLog.get('state')).to.eq('failed')
expect(lastLog.get('message')).to.contain('to be true')
expect(lastLog.get('error').message).to.include(err.message)
expect(itsLog.get('state')).to.eq('failed')
expect(itsLog.get('error').message).to.include(err.message)
done()
})
@@ -1623,9 +1540,7 @@ describe('src/cy/commands/connectors', () => {
},
}
obj.foo.bar.baz = () => {
return 'baz'
}
obj.foo.bar.baz = () => 'baz'
cy.once('fail', (err) => {
const { itsLog, lastLog } = this
@@ -1670,7 +1585,7 @@ describe('src/cy/commands/connectors', () => {
cy.on('fail', (err) => {
const { itsLog } = this
expect(err.message).to.include('Timed out retrying after 100ms: `cy.its()` errored because the property: `baz` does not exist on your subject.')
expect(err.message).to.include('Timed out retrying after 100ms: `cy.its()` errored because the property: `foo.bar.baz.fizz` does not exist on your subject.')
expect(err.docsUrl).to.eq('https://on.cypress.io/its')
expect(itsLog.get('error').message).to.include(err.message)
@@ -1686,19 +1601,19 @@ describe('src/cy/commands/connectors', () => {
cy.wrap(obj).its('foo.bar.baz.fizz')
});
[null, undefined].forEach((val) => {
[null/*, undefined*/].forEach((val) => {
it(`throws on traversed '${val}' subject`, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 100ms: \`cy.its()\` errored because the property: \`a\` returned a \`${val}\` value. The property: \`b\` does not exist on a \`${val}\` value.`)
expect(err.message).to.include('`cy.its()` waited for the specified property `b` to become accessible, but it never did.')
expect(err.message).to.include('If you do not expect the property `b` to exist, then add an assertion such as:')
expect(err.message).to.include(`\`cy.wrap({ foo: ${val} }).its('foo.baz').should('not.exist')\``)
expect(err.message).to.include(`Timed out retrying after 100ms: \`cy.its()\` errored because the property: \`a.b\` returned a \`${val}\` value.`)
expect(err.message).to.include('`cy.its()` waited for the specified property `a.b` to become accessible, but it never did.')
expect(err.message).to.include(`If you expect the property \`a.b\` to be \`${val}\`, then add an assertion such as:`)
expect(err.message).to.include(`\`cy.wrap({ foo: ${val} }).its('foo').should('be.null')\``)
expect(err.docsUrl).to.eq('https://on.cypress.io/its')
done()
})
cy.wrap({ a: val }).its('a.b.c')
cy.wrap({ a: { b: val } }).its('a.b')
})
it(`throws on initial '${val}' subject`, (done) => {
+345 -263
View File
@@ -5,134 +5,227 @@ const { Promise } = Cypress
const isWebkit = Cypress.isBrowser('webkit')
describe('src/cy/commands/cookies - no stub', () => {
context('#getCookies', () => {
// this can be removed along with the experimental flag since once the flag
// removed, clearing cookies for all domains will be done by default
beforeEach(() => {
if (!Cypress.config('experimentalSessionAndOrigin')) {
cy.clearCookies({ domain: null })
}
})
const setCookies = () => {
cy.log('set cookies')
cy.setCookie('key1', 'value1', { domain: 'www.foobar.com', log: false })
cy.setCookie('key2', 'value2', { domain: 'foobar.com', log: false })
cy.setCookie('key3', 'value3', { domain: 'www.barbaz.com', log: false })
cy.setCookie('key4', 'value4', { domain: '.www.barbaz.com', log: false })
cy.setCookie('key5', 'value5', { domain: 'barbaz.com', log: false })
cy.setCookie('key6', 'value6', { domain: '.barbaz.com', log: false })
cy.setCookie('key7', 'value7', { domain: 'www2.barbaz.com', log: false })
cy.setCookie('key8', 'value8', { domain: 'www2.foobar.com', log: false })
}
it('returns cookies from the domain matching the AUT by default', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.setCookie('baz', 'qux', { domain: 'foobar.com' })
cy.setCookie('foo', 'bar') // defaults to (super)domain: barbaz.com
cy.setCookie('qux', 'quuz', { domain: 'www.barbaz.com' })
context('#getCookies', () => {
it('returns cookies from only the bare domain matching the AUT by default when AUT is an apex domain', () => {
cy.visit('http://barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.getCookies().then((cookies) => {
expect(cookies).to.have.length(2)
// both the barbaz.com and www.barbaz.com cookies are yielded
expect(cookies[0].domain).to.match(/\.?barbaz\.com/)
expect(cookies[1].domain).to.match(/\.?www\.barbaz\.com/)
const sortedCookies = Cypress._.sortBy(cookies, 'name')
expect(sortedCookies[0].name).to.equal('key5')
expect(sortedCookies[0].domain).to.match(/\.?barbaz\.com/)
expect(sortedCookies[1].name).to.equal('key6')
expect(sortedCookies[1].domain).to.match(/\.?barbaz\.com/)
})
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://foobar.com:3500/fixtures/generic.html')
cy.getCookies().then((cookies) => {
expect(cookies).to.have.length(1)
expect(cookies[0].name).to.equal('key2')
expect(cookies[0].domain).to.match(/\.?foobar\.com/)
})
})
})
it('returns cookies from the subdomain and bare domain matching the AUT by default when AUT is a subdomain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.getCookies().then((cookies) => {
expect(cookies).to.have.length(4)
const sortedCookies = Cypress._.sortBy(cookies, 'name')
expect(sortedCookies[0].name).to.equal('key3')
expect(sortedCookies[0].domain).to.match(/\.?www\.barbaz\.com/)
expect(sortedCookies[1].name).to.equal('key4')
expect(sortedCookies[1].domain).to.match(/\.?www\.barbaz\.com/)
expect(sortedCookies[2].name).to.equal('key5')
expect(sortedCookies[2].domain).to.match(/\.?barbaz\.com/)
expect(sortedCookies[3].name).to.equal('key6')
expect(sortedCookies[3].domain).to.match(/\.?barbaz\.com/)
})
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.getCookies().then((cookies) => {
expect(cookies[0].domain).to.match(/\.?www\.foobar\.com/)
expect(cookies[1].domain).to.match(/\.?foobar\.com/)
const sortedCookies = Cypress._.sortBy(cookies, 'name')
expect(sortedCookies).to.have.length(2)
expect(sortedCookies[0].name).to.equal('key1')
expect(sortedCookies[0].domain).to.match(/\.?www\.foobar\.com/)
expect(sortedCookies[1].name).to.equal('key2')
expect(sortedCookies[1].domain).to.match(/\.?foobar\.com/)
})
})
})
it('returns cookies for the specified domain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.setCookie('foo', 'bar') // defaults to (super)domain: barbaz.com
cy.setCookie('qux', 'quuz', { domain: 'www.barbaz.com' })
cy.visit('http://barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.getCookies({ domain: 'www.foobar.com' }).then((cookies) => {
expect(cookies).to.have.length(2)
expect(cookies[0].name).to.equal('key1')
expect(cookies[0].domain).to.match(/\.?www\.foobar\.com/)
expect(cookies[1].name).to.equal('key2')
expect(cookies[1].domain).to.match(/\.?foobar\.com/)
})
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
cy.getCookies({ domain: 'barbaz.com' }).then((cookies) => {
expect(cookies).to.have.length(2)
const sortedCookies = Cypress._.sortBy(cookies, 'name')
expect(sortedCookies[0].name).to.equal('key5')
expect(sortedCookies[0].domain).to.match(/\.?barbaz\.com/)
expect(sortedCookies[1].name).to.equal('key6')
expect(sortedCookies[1].domain).to.match(/\.?barbaz\.com/)
})
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.getCookies({ domain: 'barbaz.com' }).then((cookies) => {
expect(cookies).to.have.length(2)
// both the barbaz.com and www.barbaz.com cookies are yielded
expect(cookies[0].domain).to.match(/\.?barbaz\.com/)
expect(cookies[1].domain).to.match(/\.?www\.barbaz\.com/)
cy.getCookies({ domain: 'www.barbaz.com' }).then((cookies) => {
expect(cookies).to.have.length(4)
const sortedCookies = Cypress._.sortBy(cookies, 'name')
expect(sortedCookies[0].name).to.equal('key3')
expect(sortedCookies[0].domain).to.match(/\.?www\.barbaz\.com/)
expect(sortedCookies[1].name).to.equal('key4')
expect(sortedCookies[1].domain).to.match(/\.?www\.barbaz\.com/)
expect(sortedCookies[2].name).to.equal('key5')
expect(sortedCookies[2].domain).to.match(/\.?barbaz\.com/)
expect(sortedCookies[3].name).to.equal('key6')
expect(sortedCookies[3].domain).to.match(/\.?barbaz\.com/)
})
})
})
it('returns cookies for all domains when domain is null', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.setCookie('foo', 'bar')
cy.getCookies({ domain: null }).should('have.length', 2)
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.getCookies({ domain: null }).should('have.length', 2)
})
})
})
context('#getCookie', () => {
it('returns the cookie from the domain matching the AUT by default', () => {
const setCookies = () => {
cy.log('set cookies')
cy.setCookie('key', 'www.barbaz.com value', { domain: 'www.barbaz.com', log: false })
cy.setCookie('key', 'barbaz.com value', { domain: 'barbaz.com', log: false })
cy.setCookie('key', 'www.foobar.com value', { domain: 'www.foobar.com', log: false })
cy.setCookie('key', 'foobar.com value', { domain: 'foobar.com', log: false })
}
it('returns the cookie from the domain matching the AUT by default when AUT is an apex domain', () => {
cy.visit('http://barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.getCookie('key').then((cookie) => {
expect(cookie.value).to.equal('barbaz.com value')
expect(cookie.domain).to.match(/\.?barbaz\.com/)
})
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://foobar.com:3500/fixtures/generic.html')
cy.getCookie('key').then((cookie) => {
expect(cookie.value).to.equal('foobar.com value')
expect(cookie.domain).to.match(/\.?foobar\.com/)
})
})
})
it('can return the cookie from the subdomain matching the AUT by default when AUT is a subdomain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.setCookie('foo', 'bar')
cy.setCookie('key', 'www.barbaz.com value', { domain: 'www.barbaz.com', log: false })
cy.getCookie('foo').its('domain').should('match', /\.?barbaz\.com/)
cy.getCookie('key').then((cookie) => {
expect(cookie.value).to.equal('www.barbaz.com value')
expect(cookie.domain).to.match(/\.?www\.barbaz\.com/)
})
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('key', 'www.foobar.com value', { domain: 'www.foobar.com', log: false })
cy.getCookie('foo').its('domain').should('match', /\.?www\.foobar\.com/)
cy.getCookie('key').then((cookie) => {
expect(cookie.value).to.equal('www.foobar.com value')
expect(cookie.domain).to.match(/\.?www\.foobar\.com/)
})
})
})
it('can return the cookie from the bare domain matching the AUT by default when AUT is a subdomain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('key', 'barbaz.com value', { domain: 'barbaz.com', log: false })
cy.getCookie('key').then((cookie) => {
expect(cookie.value).to.equal('barbaz.com value')
expect(cookie.domain).to.match(/\.?barbaz\.com/)
})
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('key', 'foobar.com value', { domain: 'foobar.com', log: false })
cy.getCookie('key').then((cookie) => {
expect(cookie.value).to.equal('foobar.com value')
expect(cookie.domain).to.match(/\.?foobar\.com/)
})
})
})
it('returns the cookie from the specified domain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.setCookie('foo', 'bar')
setCookies()
cy.getCookie('foo', { domain: 'www.foobar.com' })
.its('domain').should('match', /\.?www\.foobar\.com/)
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.getCookie('foo', { domain: 'barbaz.com' })
.its('domain').should('match', /\.?barbaz\.com/)
cy.getCookie('key', { domain: 'www.foobar.com' }).then((cookie) => {
expect(cookie.value).to.equal('www.foobar.com value')
expect(cookie.domain).to.match(/\.?www\.foobar\.com/)
})
})
it('returns cookie for any domain when domain is null', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.getCookie('foo', { domain: null })
.its('domain').should('match', /\.?www\.foobar\.com/)
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.clearCookie('foo')
cy.setCookie('foo', 'bar', { domain: 'barbaz.com' })
cy.getCookie('foo', { domain: null })
.its('domain').should('match', /\.?barbaz\.com/)
cy.getCookie('key', { domain: 'www.barbaz.com' }).then((cookie) => {
expect(cookie.value).to.equal('www.barbaz.com value')
expect(cookie.domain).to.match(/\.?www\.barbaz\.com/)
})
})
})
})
@@ -140,28 +233,37 @@ describe('src/cy/commands/cookies - no stub', () => {
context('#setCookie', () => {
it('sets the cookie on the domain matching the AUT by default', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('key', 'value')
cy.getCookie('foo').its('domain').should('match', /\.?barbaz\.com/)
cy.getCookie('key').its('domain').should('match', /\.?www\.barbaz\.com/)
// domain is exact
cy.getCookie('key', { domain: 'barbaz.com' }).should('be.null')
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('key', 'value')
cy.getCookie('foo').its('domain').should('equal', '.foobar.com')
cy.getCookie('key').its('domain').should('match', /\.?www\.foobar\.com/)
// domain is exact
cy.getCookie('key', { domain: 'foobar.com' }).should('be.null')
})
})
it('set the cookie on the specified domain', () => {
it('sets the cookie on the specified domain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.getCookie('foo', { domain: 'www.foobar.com' })
.its('domain').should('match', /\.?www\.foobar\.com/)
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// domain is exact
cy.getCookie('key', { domain: 'foobar.com' }).should('be.null')
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
@@ -169,170 +271,219 @@ describe('src/cy/commands/cookies - no stub', () => {
cy.getCookie('foo', { domain: 'barbaz.com' })
.its('domain').should('match', /\.?barbaz\.com/)
// domain is exact
cy.getCookie('key', { domain: 'barbaz.com' }).should('be.null')
})
})
})
context('#clearCookies', () => {
it('clears all cookies', () => {
cy.setCookie('foo', 'bar')
cy.getCookies().should('have.length', 1)
cy.clearCookies()
cy.getCookies().should('have.length', 0)
})
it('clears the cookies on the domain matching the AUT by default', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('baz', 'qux')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
it('clears cookies from only the bare domain matching the AUT by default when AUT is an apex domain', () => {
cy.visit('http://barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.clearCookies()
cy.getCookie('foo').should('be.null')
cy.getCookie('baz').should('be.null')
cy.getCookie('foo', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key1', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key2', { domain: 'foobar.com' }).should('exist')
cy.getCookie('key3', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key4', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key5').should('not.exist')
cy.getCookie('key6').should('not.exist')
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('baz', 'qux')
cy.setCookie('foo', 'bar', { domain: 'barbaz.com' })
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://foobar.com:3500/fixtures/generic.html')
// put back cookies removed above
cy.setCookie('key5', 'value5', { domain: 'barbaz.com' })
cy.setCookie('key6', 'value6', { domain: 'barbaz.com' })
cy.clearCookies()
cy.getCookie('foo').should('be.null')
cy.getCookie('baz').should('be.null')
cy.getCookie('foo', { domain: 'barbaz.com' }).should('exist')
cy.getCookie('key1', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key2').should('be.null')
cy.getCookie('key3', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key4', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key5', { domain: 'barbaz.com' }).should('exist')
cy.getCookie('key6', { domain: 'barbaz.com' }).should('exist')
})
})
it('clears cookies from the subdomain and bare domain matching the AUT by default when AUT is a subdomain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.clearCookies()
cy.getCookie('key1', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key2', { domain: 'foobar.com' }).should('exist')
cy.getCookie('key3').should('not.exist')
cy.getCookie('key4').should('not.exist')
cy.getCookie('key5').should('not.exist')
cy.getCookie('key6').should('not.exist')
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
// put back cookies removed above
cy.setCookie('key3', 'value3', { domain: 'www.barbaz.com' })
cy.setCookie('key4', 'value4', { domain: 'www.barbaz.com' })
cy.setCookie('key5', 'value5', { domain: 'barbaz.com' })
cy.setCookie('key6', 'value6', { domain: 'barbaz.com' })
cy.clearCookies()
cy.getCookie('key1').should('be.null')
cy.getCookie('key2').should('be.null')
cy.getCookie('key3', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key4', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key5', { domain: 'barbaz.com' }).should('exist')
cy.getCookie('key6', { domain: 'barbaz.com' }).should('exist')
})
})
it('clears the cookies on the specified domain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.setCookie('baz', 'qux', { domain: 'www.foobar.com' })
setCookies()
cy.clearCookies({ domain: 'www.foobar.com' })
cy.getCookie('foo', { domain: 'www.foobar.com' }).should('be.null')
cy.getCookie('baz', { domain: 'www.foobar.com' }).should('be.null')
cy.getCookie('foo').should('exist')
cy.getCookie('key1').should('be.null')
cy.getCookie('key2').should('be.null')
cy.getCookie('key3', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key4', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key5', { domain: 'barbaz.com' }).should('exist')
cy.getCookie('key6', { domain: 'barbaz.com' }).should('exist')
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('baz', 'qux', { domain: 'barbaz.com' })
// put back cookies removed above
cy.setCookie('key1', 'value1')
cy.setCookie('key2', 'value2', { domain: 'foobar.com' })
cy.clearCookies({ domain: 'barbaz.com' })
cy.clearCookies({ domain: 'www.barbaz.com' })
cy.getCookie('foo', { domain: 'barbaz.com' }).should('be.null')
cy.getCookie('baz', { domain: 'barbaz.com' }).should('be.null')
cy.getCookie('foo').should('exist')
})
})
it('clears cookies for all domains when domain is null', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.clearCookies({ domain: null })
cy.getCookies().should('have.length', 0)
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('foo', 'bar', { domain: 'barbaz.com' })
cy.clearCookies({ domain: null })
cy.getCookies().should('have.length', 0)
cy.getCookie('key1', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key2', { domain: 'foobar.com' }).should('exist')
cy.getCookie('key3').should('not.exist')
cy.getCookie('key4').should('not.exist')
cy.getCookie('key5').should('not.exist')
cy.getCookie('key6').should('not.exist')
})
})
})
context('#clearCookie', () => {
it('clears a single cookie', () => {
cy.setCookie('foo', 'bar')
cy.setCookie('key', 'val')
cy.getCookies().should('have.length', 2)
cy.clearCookie('foo')
cy.getCookies().should('have.length', 1).then((cookies) => {
expect(cookies[0].name).to.eq('key')
const setCookies = () => {
cy.log('set cookies')
cy.setCookie('key', 'www.barbaz.com value', { domain: 'www.barbaz.com', log: false })
cy.setCookie('key', 'barbaz.com value', { domain: 'barbaz.com', log: false })
cy.setCookie('key', 'www.foobar.com value', { domain: 'www.foobar.com', log: false })
cy.setCookie('key', 'foobar.com value', { domain: 'foobar.com', log: false })
}
it('clears the cookie from the domain matching the AUT by default when AUT is an apex domain', () => {
cy.visit('http://barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.clearCookie('key')
cy.getCookie('key').should('be.null')
cy.getCookie('key', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key', { domain: 'foobar.com' }).should('exist')
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://foobar.com:3500/fixtures/generic.html')
// put back cookie removed above
cy.setCookie('key', 'value1', { domain: 'barbaz.com' })
cy.clearCookie('key')
cy.getCookie('key').should('be.null')
cy.getCookie('key', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key', { domain: 'barbaz.com' }).should('exist')
cy.getCookie('key', { domain: 'www.foobar.com' }).should('exist')
})
})
it('clears the cookie on the domain matching the AUT by default', () => {
it('can clear the cookie from the subdomain matching the AUT by default when AUT is a subdomain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.setCookie('key', 'www.barbaz.com value', { domain: 'www.barbaz.com', log: false })
cy.clearCookie('foo')
cy.clearCookie('key')
cy.getCookie('foo').should('be.null')
cy.getCookie('foo', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key').should('not.exist')
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'barbaz.com' })
cy.setCookie('key', 'www.foobar.com value', { domain: 'www.foobar.com', log: false })
cy.clearCookie('foo')
cy.clearCookie('key')
cy.getCookie('foo').should('be.null')
cy.getCookie('foo', { domain: 'barbaz.com' }).should('exist')
cy.getCookie('key').should('not.exist')
})
})
it('can clear the cookie from the bare domain matching the AUT by default when AUT is a subdomain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('key', 'barbaz.com value', { domain: 'barbaz.com', log: false })
cy.clearCookie('key')
cy.getCookie('key').should('not.exist')
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('key', 'foobar.com value', { domain: 'foobar.com', log: false })
cy.clearCookie('key')
cy.getCookie('key').should('not.exist')
})
})
it('clears the cookie on the specified domain', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.visit('http://barbaz.com:3500/fixtures/generic.html')
setCookies()
cy.clearCookie('foo', { domain: 'www.foobar.com' })
cy.clearCookie('key', { domain: 'foobar.com' })
cy.getCookie('foo', { domain: 'www.foobar.com' }).should('be.null')
cy.getCookie('foo').should('exist')
cy.getCookie('key', { domain: 'foobar.com' }).should('be.null')
cy.getCookie('key', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key', { domain: 'barbaz.com' }).should('exist')
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
// webkit does not support cy.origin()
if (isWebkit) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar')
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://foobar.com:3500/fixtures/generic.html')
cy.setCookie('key', 'value1')
cy.clearCookie('foo', { domain: 'barbaz.com' })
cy.clearCookie('key', { domain: 'barbaz.com' })
cy.getCookie('foo', { domain: 'barbaz.com' }).should('be.null')
cy.getCookie('foo').should('exist')
cy.getCookie('key', { domain: 'barbaz.com' }).should('be.null')
cy.getCookie('key', { domain: 'www.barbaz.com' }).should('exist')
cy.getCookie('key', { domain: 'www.foobar.com' }).should('exist')
cy.getCookie('key', { domain: 'foobar.com' }).should('exist')
})
})
})
it('clears cookie for any domain when domain is null', () => {
cy.visit('http://www.barbaz.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'www.foobar.com' })
cy.clearCookie('foo', { domain: null })
cy.getCookies().should('have.length', 0)
if (isWebkit || !Cypress.config('experimentalSessionAndOrigin')) return
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
cy.setCookie('foo', 'bar', { domain: 'barbaz.com' })
cy.clearCookie('foo', { domain: null })
cy.getCookies().should('have.length', 0)
})
})
})
describe('src/cy/commands/cookies', () => {
@@ -1340,35 +1491,7 @@ describe('src/cy/commands/cookies', () => {
})
})
it('calls \'clear:cookies\' only with clearableCookies', () => {
Cypress.automation
.withArgs('get:cookies')
.resolves([
{ name: 'foo' },
{ name: 'bar' },
])
.withArgs('clear:cookies', [
{ name: 'foo', domain: 'localhost' },
])
.resolves({
name: 'foo',
})
cy.stub(Cypress.Cookies, 'getClearableCookies')
.withArgs([{ name: 'foo' }, { name: 'bar' }])
.returns([{ name: 'foo' }])
cy.clearCookies().should('be.null').then(() => {
expect(Cypress.automation).to.be.calledWith(
'clear:cookies',
[{ name: 'foo', domain: 'localhost' }],
)
})
})
it('calls \'clear:cookies\' with all cookies', () => {
Cypress.Cookies.preserveOnce('bar', 'baz')
Cypress.automation
.withArgs('get:cookies')
.resolves([
@@ -1376,12 +1499,6 @@ describe('src/cy/commands/cookies', () => {
{ name: 'bar' },
{ name: 'baz' },
])
.withArgs('clear:cookies', [
{ name: 'foo', domain: 'localhost' },
])
.resolves({
name: 'foo',
})
.withArgs('clear:cookies', [
{ name: 'foo', domain: 'localhost' },
{ name: 'bar', domain: 'localhost' },
@@ -1393,11 +1510,6 @@ describe('src/cy/commands/cookies', () => {
cy
.clearCookies().should('be.null').then(() => {
expect(Cypress.automation).to.be.calledWith(
'clear:cookies',
[{ name: 'foo', domain: 'localhost' }],
)
}).clearCookies().should('be.null').then(() => {
expect(Cypress.automation).to.be.calledWith(
'clear:cookies', [
{ name: 'foo', domain: 'localhost' },
@@ -1659,34 +1771,4 @@ describe('src/cy/commands/cookies', () => {
})
})
})
context('Cypress.Cookies.defaults', () => {
it('throws error on use of renamed whitelist option', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`Cypress.Cookies.defaults` `whitelist` option has been renamed to `preserve`. Please rename `whitelist` to `preserve`.')
done()
})
Cypress.Cookies.defaults({
whitelist: 'session_id',
})
})
it('logs deprecation warning', () => {
cy.stub(Cypress.utils, 'warning')
Cypress.Cookies.defaults({})
expect(Cypress.utils.warning).to.be.calledWith('`Cypress.Cookies.defaults()` has been deprecated and will be removed in a future release. Consider using `cy.session()` instead.\n\nhttps://on.cypress.io/session')
})
})
context('Cypress.Cookies.preserveOnce', () => {
it('logs deprecation warning', () => {
cy.stub(Cypress.utils, 'warning')
Cypress.Cookies.preserveOnce({})
expect(Cypress.utils.warning).to.be.calledWith('`Cypress.Cookies.preserveOnce()` has been deprecated and will be removed in a future release. Consider using `cy.session()` instead.\n\nhttps://on.cypress.io/session')
})
})
})
@@ -517,9 +517,7 @@ describe('src/cy/commands/location', () => {
const { lastLog } = this
_.each(obj, (value, key) => {
expect(lastLog.get(key)).to.deep.eq(value)
})
expect(_.pick(lastLog.attributes, ['name', 'message'])).to.eql(obj)
})
})
+21 -10
View File
@@ -10,6 +10,11 @@ describe('src/cy/commands/misc', () => {
it('nulls out the subject', () => {
cy.noop({}).end().then((subject) => {
expect(subject).to.be.null
// We want cy.end() to break the subject chain - any previous entries
// (in this case `{}`) should be discarded. No re-running any previous
// query functions once you've used `.end()` on a chain.
expect(cy.subjectChain()).to.eql([null])
})
})
})
@@ -18,6 +23,11 @@ describe('src/cy/commands/misc', () => {
it('nulls out the subject', () => {
cy.wrap({}).log('foo').then((subject) => {
expect(subject).to.be.null
// We want cy.end() to break the subject chain - any previous entries
// (in this case `{}`) should be discarded. No re-running any previous
// query functions once you've used `.end()` on a chain.
expect(cy.subjectChain()).to.eql([null])
})
})
@@ -35,16 +45,19 @@ describe('src/cy/commands/misc', () => {
it('logs immediately', function (done) {
cy.on('log:added', (attrs, log) => {
cy.removeAllListeners('log:added')
if (log.get('name') === 'log') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq('foo, {foo: bar}')
expect(log.get('name')).to.eq('log')
expect(log.get('end')).to.be.true
expect(log.get('message')).to.eq('foo, {foo: bar}')
expect(log.get('end')).to.be.true
done()
done()
}
})
cy.log('foo', { foo: 'bar' }).then(() => {
// Query the 'body' here to verify that .log()
// ignores the previous subject and logs only its arguments.
cy.get('body').log('foo', { foo: 'bar' }).then(() => {
const { lastLog } = this
expect(lastLog.get('ended')).to.be.true
@@ -229,9 +242,8 @@ describe('src/cy/commands/misc', () => {
it('throws when wrapping an array of windows', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.scrollTo()` failed because it requires a DOM element.')
expect(err.message).to.include('`cy.scrollTo()` failed because it requires a DOM element or window.')
expect(err.message).to.include('[<window>]')
expect(err.message).to.include('All 2 subject validations failed on this subject.')
done()
})
@@ -243,9 +255,8 @@ describe('src/cy/commands/misc', () => {
it('throws when wrapping an array of documents', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.screenshot()` failed because it requires a DOM element.')
expect(err.message).to.include('`cy.screenshot()` failed because it requires a DOM element, window or document.')
expect(err.message).to.include('[<document>]')
expect(err.message).to.include('All 3 subject validations failed on this subject.')
done()
})
@@ -581,34 +581,18 @@ describe('src/cy/commands/navigation', () => {
})
})
if (!Cypress.config('experimentalSessionAndOrigin')) {
it('removes window:load listeners 2x for about:blank and first url visit when experimentalSessionAndOrigin=false', () => {
describe('removes window:load listeners when testIsolation=true', () => {
it('removes for first url visit', () => {
const listeners = cy.listeners('window:load')
const winLoad = cy.spy(cy, 'once').withArgs('window:load')
cy.visit('/fixtures/generic.html').then(() => {
// once for about:blank, once for $iframe src
expect(winLoad).to.be.calledTwice
expect(winLoad).to.be.calledOnce
expect(cy.listeners('window:load')).to.deep.eq(listeners)
})
})
}
if (Cypress.config('experimentalSessionAndOrigin')) {
describe('removes window:load listeners for first url visit when experimentalSessionAndOrigin=true', () => {
it('removes for first url visit', () => {
const listeners = cy.listeners('window:load')
const winLoad = cy.spy(cy, 'once').withArgs('window:load')
cy.visit('/fixtures/generic.html').then(() => {
expect(winLoad).to.be.calledOnce // once for $iframe src
expect(cy.listeners('window:load')).to.deep.eq(listeners)
})
})
})
}
})
it('can visit pages on the same origin', () => {
cy
@@ -617,6 +601,32 @@ describe('src/cy/commands/navigation', () => {
.visit('http://localhost:3500/fixtures/dimensions.html')
})
it('can visit a 2nd domain on different port', function () {
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('http://localhost:3501/fixtures/generic.html')
})
it('can visit a 2nd domain on different protocol', function () {
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('https://localhost:3502/fixtures/generic.html')
})
it('can visit a 2nd domain on different superdomain', function () {
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
})
it('can visit 2 unique ip addresses', function () {
cy
.visit('http://127.0.0.1:3500/fixtures/generic.html')
.visit('http://0.0.0.0:3500/fixtures/generic.html')
})
it('can navigate to a cross origin', { pageLoadTimeout: 3000 }, function () {
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
it('resolves the subject to the remote iframe window', () => {
cy.visit('/fixtures/jquery.html').then((win) => {
expect(win).to.eq(cy.state('$autIframe').prop('contentWindow'))
@@ -768,83 +778,41 @@ describe('src/cy/commands/navigation', () => {
cy.visit('fixtures/redirection-loop-a.html')
})
if (!Cypress.config('experimentalSessionAndOrigin')) {
describe('when only hashes are changing when experimentalSessionAndOrigin=false', () => {
it('short circuits the visit if the page will not refresh', () => {
let count = 0
const urls = []
describe('when only hashes are changing when testIsolation=true', () => {
it('short circuits the visit if the page will not refresh', () => {
let count = 0
const urls = []
cy.on('window:load', () => {
urls.push(cy.state('window').location.href)
cy.on('window:load', () => {
urls.push(cy.state('window').location.href)
count += 1
})
count += 1
})
cy
// about:blank yes (1)
.visit('/fixtures/generic.html?foo#bar') // yes (2)
.visit('/fixtures/generic.html?foo#foo') // no (2)
.visit('/fixtures/generic.html?bar#bar') // yes (3)
.visit('/fixtures/dimensions.html?bar#bar') // yes (4)
.visit('/fixtures/dimensions.html?baz#bar') // yes (5)
.visit('/fixtures/dimensions.html#bar') // yes (6)
.visit('/fixtures/dimensions.html') // yes (7)
.visit('/fixtures/dimensions.html#baz') // no (7)
.visit('/fixtures/dimensions.html#') // no (7)
.then(() => {
expect(count).to.eq(7)
cy
.visit('/fixtures/generic.html?foo#bar') // yes (1)
.visit('/fixtures/generic.html?foo#foo') // no (1)
.visit('/fixtures/generic.html?bar#bar') // yes (2)
.visit('/fixtures/dimensions.html?bar#bar') // yes (3)
.visit('/fixtures/dimensions.html?baz#bar') // yes (4)
.visit('/fixtures/dimensions.html#bar') // yes (5)
.visit('/fixtures/dimensions.html') // yes (6)
.visit('/fixtures/dimensions.html#baz') // no (6)
.visit('/fixtures/dimensions.html#') // no (6)
.then(() => {
expect(count).to.eq(6)
expect(urls).to.deep.eq([
'about:blank',
'http://localhost:3500/fixtures/generic.html?foo#bar',
'http://localhost:3500/fixtures/generic.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?baz#bar',
'http://localhost:3500/fixtures/dimensions.html#bar',
'http://localhost:3500/fixtures/dimensions.html',
])
})
expect(urls).to.deep.eq([
'http://localhost:3500/fixtures/generic.html?foo#bar',
'http://localhost:3500/fixtures/generic.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?baz#bar',
'http://localhost:3500/fixtures/dimensions.html#bar',
'http://localhost:3500/fixtures/dimensions.html',
])
})
})
}
if (Cypress.config('experimentalSessionAndOrigin')) {
describe('when only hashes are changing when experimentalSessionAndOrigin=true', () => {
it('short circuits the visit if the page will not refresh', () => {
let count = 0
const urls = []
cy.on('window:load', () => {
urls.push(cy.state('window').location.href)
count += 1
})
cy
.visit('/fixtures/generic.html?foo#bar') // yes (1)
.visit('/fixtures/generic.html?foo#foo') // no (1)
.visit('/fixtures/generic.html?bar#bar') // yes (2)
.visit('/fixtures/dimensions.html?bar#bar') // yes (3)
.visit('/fixtures/dimensions.html?baz#bar') // yes (4)
.visit('/fixtures/dimensions.html#bar') // yes (5)
.visit('/fixtures/dimensions.html') // yes (6)
.visit('/fixtures/dimensions.html#baz') // no (6)
.visit('/fixtures/dimensions.html#') // no (6)
.then(() => {
expect(count).to.eq(6)
expect(urls).to.deep.eq([
'http://localhost:3500/fixtures/generic.html?foo#bar',
'http://localhost:3500/fixtures/generic.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?bar#bar',
'http://localhost:3500/fixtures/dimensions.html?baz#bar',
'http://localhost:3500/fixtures/dimensions.html#bar',
'http://localhost:3500/fixtures/dimensions.html',
])
})
})
})
}
})
// https://github.com/cypress-io/cypress/issues/1311
// TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23201
@@ -1015,10 +983,10 @@ describe('src/cy/commands/navigation', () => {
expect(win.location.href).to.include('/fixtures/jquery.html?foo=bar#dashboard?baz=quux')
})
this.win = cy.state('window')
this.cyWin = cy.state('window')
this.eq = (attr, str) => {
expect(this.win.location[attr]).to.eq(str)
expect(this.cyWin.location[attr]).to.eq(str)
}
})
@@ -1469,159 +1437,6 @@ describe('src/cy/commands/navigation', () => {
})
})
it('throws when attempting to visit a 2nd domain on different port', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
const experimentalMessage = Cypress.config('experimentalSessionAndOrigin') ? `You likely forgot to use \`cy.origin()\`:\n` : `In order to visit a different origin, you can enable the \`experimentalSessionAndOrigin\` flag and use \`cy.origin()\`:\n`
expect(err.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
${experimentalMessage}
\`cy.visit('http://localhost:3500/fixtures/generic.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://localhost:3501', () => {\`
\` cy.visit('http://localhost:3501/fixtures/generic.html')\`
\` <commands targeting http://localhost:3501 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> port\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://localhost:3500'\n
You're attempting to visit this URL:\n
> 'http://localhost:3501'`)
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
assertLogLength(this.logs, 2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('http://localhost:3501/fixtures/generic.html')
// If experimentalSessionAndOrigin is enabled this is no longer an error
if (Cypress.config('experimentalSessionAndOrigin')) {
done()
}
})
it('throws when attempting to visit a 2nd domain on different protocol', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
const experimentalMessage = Cypress.config('experimentalSessionAndOrigin') ? `You likely forgot to use \`cy.origin()\`:\n` : `In order to visit a different origin, you can enable the \`experimentalSessionAndOrigin\` flag and use \`cy.origin()\`:\n`
expect(err.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
${experimentalMessage}
\`cy.visit('http://localhost:3500/fixtures/generic.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('https://localhost:3502', () => {\`
\` cy.visit('https://localhost:3502/fixtures/generic.html')\`
\` <commands targeting https://localhost:3502 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> protocol, port\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://localhost:3500'\n
You're attempting to visit this URL:\n
> 'https://localhost:3502'`)
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
assertLogLength(this.logs, 2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('https://localhost:3502/fixtures/generic.html')
// If experimentalSessionAndOrigin is enabled this is no longer an error
if (Cypress.config('experimentalSessionAndOrigin')) {
done()
}
})
it('throws when attempting to visit a 2nd domain on different superdomain', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
const experimentalMessage = Cypress.config('experimentalSessionAndOrigin') ? `You likely forgot to use \`cy.origin()\`:\n` : `In order to visit a different origin, you can enable the \`experimentalSessionAndOrigin\` flag and use \`cy.origin()\`:\n`
expect(err.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
${experimentalMessage}
\`cy.visit('http://localhost:3500/fixtures/generic.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://www.foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/generic.html')\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://localhost:3500'\n
You're attempting to visit this URL:\n
> 'http://www.foobar.com:3500'`)
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
assertLogLength(this.logs, 2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('http://localhost:3500/fixtures/generic.html')
cy.visit('http://www.foobar.com:3500/fixtures/generic.html')
// If experimentalSessionAndOrigin is enabled this is no longer an error
if (Cypress.config('experimentalSessionAndOrigin')) {
done()
}
})
it('throws attempting to visit 2 unique ip addresses', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
const experimentalMessage = Cypress.config('experimentalSessionAndOrigin') ? `You likely forgot to use \`cy.origin()\`:\n` : `In order to visit a different origin, you can enable the \`experimentalSessionAndOrigin\` flag and use \`cy.origin()\`:\n`
expect(err.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
${experimentalMessage}
\`cy.visit('http://127.0.0.1:3500/fixtures/generic.html')\`
\`<commands targeting http://127.0.0.1:3500 go here>\`\n
\`cy.origin('http://0.0.0.0:3500', () => {\`
\` cy.visit('http://0.0.0.0:3500/fixtures/generic.html')\`
\` <commands targeting http://0.0.0.0:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://127.0.0.1:3500'\n
You're attempting to visit this URL:\n
> 'http://0.0.0.0:3500'`)
expect(err.docsUrl).to.eq('https://on.cypress.io/cannot-visit-different-origin-domain')
assertLogLength(this.logs, 2)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy
.visit('http://127.0.0.1:3500/fixtures/generic.html')
.visit('http://0.0.0.0:3500/fixtures/generic.html')
// If experimentalSessionAndOrigin is enabled this is no longer an error
if (Cypress.config('experimentalSessionAndOrigin')) {
done()
}
})
it('displays loading_network_failed when _resolveUrl throws', function (done) {
const err1 = new Error('connect ECONNREFUSED 127.0.0.1:64646')
@@ -2233,42 +2048,6 @@ describe('src/cy/commands/navigation', () => {
.get('#does-not-exist', { timeout: 200 }).should('have.class', 'foo')
})
it('displays cross origin failures when navigating to a cross origin', { pageLoadTimeout: 3000 }, function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
const error = Cypress.isBrowser('firefox') ? 'Permission denied to get property "href" on cross-origin object' : 'Blocked a frame with origin "http://localhost:3500" from accessing a cross-origin frame.'
// When the experimentalSessionAndOrigin feature is disabled, we will immediately and display this message.
expect(err.message).to.contain(stripIndent`\
Cypress detected a cross origin error happened on page load:\n
> ${error}\n
Before the page load, you were bound to the origin:\n
> http://localhost:3500\n
A cross origin error happens when your application navigates to a new URL which does not match the origin above.\n
A new URL does not match the origin if the 'protocol', 'port' (if specified), and/or 'host' are different.\n
Cypress does not allow you to navigate to a different origin URL within a single test.\n
You may need to restructure some of your test code to avoid this problem.\n
Alternatively you can also disable Chrome Web Security in Chromium-based browsers which will turn off this restriction by setting { chromeWebSecurity: false }`)
expect(err.message).to.contain(`packages/driver/cypress.config.ts`)
expect(err.docsUrl).to.eq('https://on.cypress.io/cross-origin-violation')
assertLogLength(this.logs, 7)
expect(lastLog.get('error')).to.eq(err)
done()
})
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
// If experimentalSessionAndOrigin is enabled this is no longer an error
if (Cypress.config('experimentalSessionAndOrigin')) {
done()
}
})
return null
})
})
@@ -2345,7 +2124,7 @@ describe('src/cy/commands/navigation', () => {
expect(this.lastLog).to.exist
expect(this.lastLog.get('state')).to.eq('pending')
expect(this.lastLog.get('message')).to.eq('--waiting for new page to load--')
expect(this.lastLog.get('snapshots')).to.not.exist
expect(this.lastLog.get('snapshots')).to.have.length(0)
})
}).get('#dimensions').click()
.then(function () {
@@ -2373,7 +2152,7 @@ describe('src/cy/commands/navigation', () => {
expect(this.lastLog).to.exist
expect(this.lastLog.get('state')).to.eq('pending')
expect(this.lastLog.get('message')).to.eq('--waiting for new page to load--')
expect(this.lastLog.get('snapshots')).to.not.exist
expect(this.lastLog.get('snapshots')).to.have.length(0)
})
cy
@@ -2594,206 +2373,101 @@ describe('src/cy/commands/navigation', () => {
})
})
if (!Cypress.config('experimentalSessionAndOrigin')) {
describe('filters page load events when going back with window navigation when experimentalSessionAndOrigin=false', () => {
describe('filters page load events when going back with window navigation when testIsolation=true', () => {
// https://github.com/cypress-io/cypress/issues/19230
it('when going back with window navigation', () => {
const emit = cy.spy(Cypress, 'emit').log(false).withArgs('navigation:changed')
it('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) => {
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.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.getCall(0)).to.be.calledWith(
'navigation:changed',
'page navigation event (load)',
)
expect(emit.getCall(1)).to.be.calledWith(
'navigation:changed',
'page navigation event (before:load)',
)
expect(emit.getCall(2)).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)
win.history.forward()
})
})
})
})
}
if (Cypress.config('experimentalSessionAndOrigin')) {
describe('filters page load events when going back with window navigation when experimentalSessionAndOrigin=true', () => {
// https://github.com/cypress-io/cypress/issues/19230
it('when going back with window navigation', () => {
const emit = cy.spy(Cypress, 'emit').log(false).withArgs('navigation:changed')
cy.get('#dimensions').click()
.window().then((win) => {
return new Promise((resolve) => {
cy.on('navigation:changed', (event) => {
if (event.includes('(load)')) {
resolve()
}
})
cy
.visit('/fixtures/generic.html')
.get('#hashchange').click()
.window().then((win) => {
win.history.back()
})
.then(() => {
return new Promise((resolve) => {
cy.once('navigation:changed', resolve)
cy.on('navigation:changed', resolve)
win.history.back()
}).then(() => {
return new Promise((resolve) => {
cy.once('navigation:changed', resolve)
win.history.forward()
})
})
})
.then(() => {
expect(emit.getCall(0)).to.be.calledWith(
'navigation:changed',
'page navigation event (before:load)',
)
cy.get('#dimensions').click()
.window().then((win) => {
return new Promise((resolve) => {
cy.on('navigation:changed', (event) => {
if (event.includes('(load)')) {
resolve()
}
})
expect(emit.getCall(1)).to.be.calledWith(
'navigation:changed',
'page navigation event (load)',
)
win.history.back()
})
.then(() => {
return new Promise((resolve) => {
cy.on('navigation:changed', resolve)
win.history.back()
})
})
.then(() => {
expect(emit.getCall(0)).to.be.calledWith(
'navigation:changed',
'page navigation event (before:load)',
)
expect(emit.getCall(2)).to.be.calledWith(
'navigation:changed',
'hashchange',
)
expect(emit.getCall(1)).to.be.calledWith(
'navigation:changed',
'page navigation event (load)',
)
expect(emit.getCall(3)).to.be.calledWithMatch(
'navigation:changed',
'hashchange',
)
expect(emit.getCall(2)).to.be.calledWith(
'navigation:changed',
'hashchange',
)
expect(emit.getCall(4)).to.be.calledWithMatch(
'navigation:changed',
'hashchange',
)
expect(emit.getCall(3)).to.be.calledWithMatch(
'navigation:changed',
'hashchange',
)
expect(emit.getCall(5)).to.be.calledWithMatch(
'navigation:changed',
'page navigation event (before:load)',
)
expect(emit.getCall(4)).to.be.calledWithMatch(
'navigation:changed',
'hashchange',
)
expect(emit.getCall(6)).to.be.calledWith(
'navigation:changed',
'page navigation event (load)',
)
expect(emit.getCall(5)).to.be.calledWithMatch(
'navigation:changed',
'page navigation event (before:load)',
)
expect(emit.getCall(7)).to.be.calledWith(
'navigation:changed',
'page navigation event (before:load)',
)
expect(emit.getCall(6)).to.be.calledWith(
'navigation:changed',
'page navigation event (load)',
)
expect(emit.getCall(8)).to.be.calledWith(
'navigation:changed',
'page navigation event (load)',
)
expect(emit.getCall(7)).to.be.calledWith(
'navigation:changed',
'page navigation event (before:load)',
)
expect(emit.getCall(9)).to.be.calledWith(
'navigation:changed',
'hashchange',
)
expect(emit.getCall(8)).to.be.calledWith(
'navigation:changed',
'page navigation event (load)',
)
expect(emit.getCall(9)).to.be.calledWith(
'navigation:changed',
'hashchange',
)
expect(emit.callCount).to.eq(10)
})
expect(emit.callCount).to.eq(10)
})
})
})
}
})
it('logs url changed event', () => {
cy
@@ -95,7 +95,7 @@ describe('src/cy/commands/querying', () => {
cy.get('body').focused().then(function () {
const { lastLog } = this
expect(lastLog.get('type')).to.eq('parent')
expect(lastLog.get('type')).not.to.eq('child')
})
})
@@ -123,7 +123,7 @@ describe('src/cy/commands/querying', () => {
cy.get('input:first').focused().then(function ($input) {
const { lastLog } = this
expect(lastLog.get('$el')).to.eq($input)
expect(lastLog.get('$el')).to.eql($input)
})
})
@@ -18,23 +18,6 @@ describe('src/cy/commands/querying', () => {
})
})
// NOTE: FLAKY in CI, need to investigate further
it.skip('retries finding elements until something is found', () => {
const missingEl = $('<div />', { id: 'missing-el' })
// wait until we're ALMOST about to time out before
// appending the missingEl
cy.on('command:retry', (options) => {
if ((options.total + (options._interval * 4)) > options._runnableTimeout) {
cy.$$('body').append(missingEl)
}
})
cy.get('#missing-el').then(($div) => {
expect($div).to.match(missingEl)
})
})
it('can increase the timeout', () => {
const missingEl = $('<div />', { id: 'missing-el' })
@@ -287,7 +270,7 @@ describe('src/cy/commands/querying', () => {
})
})
it('retries an alias when too many elements found without replaying commands', () => {
it('retries an alias when too many elements found', () => {
// add 500ms to the delta
cy.timeout(500, true)
@@ -295,24 +278,15 @@ describe('src/cy/commands/querying', () => {
const length = buttons.length - 2
const replayCommandsFrom = cy.spy(cy, 'replayCommandsFrom')
cy.on('command:retry', () => {
buttons.last().remove()
buttons = cy.$$('button')
})
const existingLen = cy.queue.length
// should eventually resolve after adding 1 button
cy
.get('button').as('btns')
.get('@btns').should('have.length', length).then(($buttons) => {
expect(replayCommandsFrom).not.to.be.called
// get, as, get, should, then == 5
expect(cy.queue.length - existingLen).to.eq(5) // we should not have replayed any commands
expect($buttons.length).to.eq(length)
})
})
@@ -380,10 +354,9 @@ describe('src/cy/commands/querying', () => {
})
})
it('logs route aliases', () => {
it('logs intercept aliases', () => {
cy.visit('http://localhost:3500/fixtures/jquery.html')
cy.server()
cy.route(/users/, {}).as('get.users')
cy.intercept(/users/, {}).as('get.users')
cy.window().then({ timeout: 2000 }, (win) => {
win.$.get('/users')
})
@@ -392,35 +365,27 @@ describe('src/cy/commands/querying', () => {
expect(this.lastLog.pick('message', 'referencesAlias', 'aliasType')).to.deep.eq({
message: '@get.users',
referencesAlias: { name: 'get.users' },
aliasType: 'route',
aliasType: 'intercept',
})
})
})
it('logs primitive aliases', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'get') {
expect(log.pick('$el', 'numRetries', 'referencesAlias', 'aliasType')).to.deep.eq({
referencesAlias: { name: 'f' },
aliasType: 'primitive',
})
done()
}
it('logs primitive aliases', () => {
cy.noop('foo').as('f')
.get('@f').then(function () {
expect(this.lastLog.pick('$el', 'numRetries', 'referencesAlias', 'aliasType')).to.deep.eq({
referencesAlias: { name: 'f' },
aliasType: 'primitive',
})
})
cy
.noop('foo').as('f')
.get('@f')
})
it('logs immediately before resolving', (done) => {
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'get') {
expect(log.pick('state', 'referencesAlias', 'aliasType')).to.deep.eq({
expect(log.pick('state', 'referencesAlias')).to.deep.eq({
state: 'pending',
referencesAlias: undefined,
aliasType: 'dom',
})
done()
@@ -453,10 +418,10 @@ describe('src/cy/commands/querying', () => {
referencesAlias: undefined,
}
expect(this.lastLog.get('$el').get(0)).to.eq($body.get(0))
expect(this.lastLog.get('$el')).to.eql($body)
_.each(obj, (value, key) => {
expect(this.lastLog.get(key)).deep.eq(value, `expected key: ${key} to eq value: ${value}`)
expect(this.lastLog.get(key)).to.eq(value, `expected key: ${key} to eq value: ${value}`)
})
})
})
@@ -493,14 +458,13 @@ describe('src/cy/commands/querying', () => {
})
})
it('#consoleProps with a route alias', () => {
it('#consoleProps with an intercept alias', () => {
cy
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.visit('http://localhost:3500/fixtures/jquery.html')
.window().then({ timeout: 2000 }, (win) => {
return win.$.get('/users')
}).get('@getUsers').then(function (obj) {
}).wait('@getUsers').get('@getUsers').then(function (obj) {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
Command: 'get',
Alias: '@getUsers',
@@ -556,36 +520,33 @@ describe('src/cy/commands/querying', () => {
})
})
describe('route aliases', () => {
describe('intercept aliases', () => {
it('returns the xhr', () => {
cy
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.visit('http://localhost:3500/fixtures/jquery.html')
.window().then({ timeout: 2000 }, (win) => {
return win.$.get('/users')
}).get('@getUsers').then((xhr) => {
expect(xhr.url).to.include('/users')
}).wait('@getUsers').get('@getUsers').then((xhr) => {
expect(xhr.response.url).to.include('/users')
})
})
it('handles dots in alias name', () => {
cy.server()
cy.route(/users/, {}).as('get.users')
cy.intercept(/users/, {}).as('get.users')
cy.visit('http://localhost:3500/fixtures/jquery.html')
cy.window().then({ timeout: 2000 }, (win) => {
return win.$.get('/users')
})
cy.get('@get.users').then((xhr) => {
expect(xhr.url).to.include('/users')
cy.wait('@get.users').get('@get.users').then((xhr) => {
expect(xhr.response.url).to.include('/users')
})
})
it('returns null if no xhr is found', () => {
cy
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.visit('http://localhost:3500/fixtures/jquery.html')
.get('@getUsers').then((xhr) => {
expect(xhr).to.be.null
@@ -595,25 +556,23 @@ describe('src/cy/commands/querying', () => {
it('returns an array of xhrs', () => {
cy
.visit('http://localhost:3500/fixtures/jquery.html')
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.window().then({ timeout: 2000 }, (win) => {
return Promise.all([
win.$.get('/users', { num: 1 }),
win.$.get('/users', { num: 2 }),
])
}).get('@getUsers.all').then((xhrs) => {
}).wait('@getUsers').wait('@getUsers').get('@getUsers.all').then((xhrs) => {
expect(xhrs).to.be.an('array')
expect(xhrs[0].url).to.include('/users?num=1')
expect(xhrs[0].response.url).to.include('/users?num=1')
expect(xhrs[1].url).to.include('/users?num=2')
expect(xhrs[1].response.url).to.include('/users?num=2')
})
})
it('returns an array of xhrs when dots in alias name', () => {
cy.visit('http://localhost:3500/fixtures/jquery.html')
cy.server()
cy.route(/users/, {}).as('get.users')
cy.intercept(/users/, {}).as('get.users')
cy.window().then({ timeout: 2000 }, (win) => {
return Promise.all([
win.$.get('/users', { num: 1 }),
@@ -621,48 +580,45 @@ describe('src/cy/commands/querying', () => {
])
})
cy.get('@get.users.all').then((xhrs) => {
cy.wait('@get.users').wait('@get.users').get('@get.users.all').then((xhrs) => {
expect(xhrs).to.be.an('array')
expect(xhrs[0].url).to.include('/users?num=1')
expect(xhrs[0].response.url).to.include('/users?num=1')
expect(xhrs[1].url).to.include('/users?num=2')
expect(xhrs[1].response.url).to.include('/users?num=2')
})
})
it('returns the 1st xhr', () => {
cy
.visit('http://localhost:3500/fixtures/jquery.html')
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.window().then({ timeout: 2000 }, (win) => {
return Promise.all([
win.$.get('/users', { num: 1 }),
win.$.get('/users', { num: 2 }),
])
}).get('@getUsers.1').then((xhr1) => {
expect(xhr1.url).to.include('/users?num=1')
}).wait('@getUsers').wait('@getUsers').get('@getUsers.1').then((xhr1) => {
expect(xhr1.response.url).to.include('/users?num=1')
})
})
it('returns the 2nd xhr', () => {
cy
.visit('http://localhost:3500/fixtures/jquery.html')
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.window().then({ timeout: 2000 }, (win) => {
return Promise.all([
win.$.get('/users', { num: 1 }),
win.$.get('/users', { num: 2 }),
])
}).get('@getUsers.2').then((xhr2) => {
expect(xhr2.url).to.include('/users?num=2')
}).wait('@getUsers').wait('@getUsers').get('@getUsers.2').then((xhr2) => {
expect(xhr2.response.url).to.include('/users?num=2')
})
})
it('returns the 2nd xhr when dots in alias', () => {
cy.visit('http://localhost:3500/fixtures/jquery.html')
cy.server()
cy.route(/users/, {}).as('get.users')
cy.intercept(/users/, {}).as('get.users')
cy.window().then({ timeout: 2000 }, (win) => {
return Promise.all([
win.$.get('/users', { num: 1 }),
@@ -670,68 +626,27 @@ describe('src/cy/commands/querying', () => {
])
})
cy.get('@get.users.2').then((xhr2) => {
expect(xhr2.url).to.include('/users?num=2')
cy.wait('@get.users').wait('@get.users').get('@get.users.2').then((xhr2) => {
expect(xhr2.response.url).to.include('/users?num=2')
})
})
it('returns the 3rd xhr as null', () => {
cy
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.visit('http://localhost:3500/fixtures/jquery.html')
.window().then({ timeout: 2000 }, (win) => {
return Promise.all([
win.$.get('/users', { num: 1 }),
win.$.get('/users', { num: 2 }),
])
}).get('@getUsers.3').then((xhr3) => {
}).wait('@getUsers').wait('@getUsers').get('@getUsers.3').then((xhr3) => {
expect(xhr3).to.be.null
})
})
})
})
// it "re-queries the dom if any element in an alias isnt visible", ->
// inputs = cy.$$("input")
// inputs.hide()
// cy
// .get("input", {visible: false}).as("inputs").then ($inputs) ->
// @length = $inputs.length
// ## show the inputs
// $inputs.show()
// return $inputs
// .get("@inputs").then ($inputs) ->
// ## we should have re-queried for these inputs
// ## which should have increased their length by 1
// expect($inputs).to.have.length(@length)
// these other tests are for .save
// it "will resolve deferred arguments", ->
// df = $.Deferred()
// _.delay ->
// df.resolve("iphone")
// , 100
// cy.get("input:text:first").type(df).then ($input) ->
// expect($input).to.have.value("iphone")
// it "handles saving subjects", ->
// cy.noop({foo: "foo"}).assign("foo").noop(cy.get("foo")).then (subject) ->
// expect(subject).to.deep.eq {foo: "foo"}
// it "resolves falsy arguments", ->
// cy.noop(0).assign("zero").then ->
// expect(cy.get("zero")).to.eq 0
// it "returns a function when no alias was found", ->
// cy.noop().then ->
// expect(cy.get("something")).to.be.a("function")
describe('errors', {
defaultCommandTimeout: 50,
}, () => {
@@ -840,8 +755,7 @@ describe('src/cy/commands/querying', () => {
})
cy
.server()
.route(/json/, { foo: 'foo' }).as('getJSON')
.intercept(/json/, { foo: 'foo' }).as('getJSON')
.visit('http://localhost:3500/fixtures/xhr.html').then(() => {
cy.$$('#get-json').click(() => {
cy.timeout(1000)
@@ -974,34 +888,31 @@ describe('src/cy/commands/querying', () => {
})
cy
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.get('@getUsers.0')
})
it('throws when alias property isnt just a digit', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`1b` is not a valid alias property. Only `numbers` or `all` is permitted.')
expect(err.message).to.include('could not find a registered alias for: `@getUsers.1b`')
done()
})
cy
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.get('@getUsers.1b')
})
it('throws when alias property isnt a digit or `all`', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`all ` is not a valid alias property. Only `numbers` or `all` is permitted.')
expect(err.message).to.include('could not find a registered alias for: `@getUsers.all `')
done()
})
cy
.server()
.route(/users/, {}).as('getUsers')
.intercept(/users/, {}).as('getUsers')
.get('@getUsers.all ')
})
@@ -1089,7 +1000,7 @@ describe('src/cy/commands/querying', () => {
})
})
it('GET is scoped to the current subject', () => {
it('is scoped to the current subject', () => {
const span = cy.$$('#click-me a span')
cy.get('#click-me a').contains('click').then(($span) => {
@@ -1232,23 +1143,9 @@ describe('src/cy/commands/querying', () => {
})
})
it('finds text by regexp and restores contains', () => {
const { contains } = Cypress.$Cypress.$.expr[':']
cy.contains(/^asdf \d+/).then(($li) => {
expect($li).to.have.text('asdf 1')
expect(Cypress.$Cypress.$.expr[':'].contains).to.eq(contains)
})
})
it('finds text by regexp when second parameter is a regexp and restores contains', () => {
const { contains } = Cypress.$Cypress.$.expr[':']
it('finds text by regexp when second parameter is a regexp', () => {
cy.contains('#asdf>li:first', /asdf 1/).then(($li) => {
expect($li).to.have.text('asdf 1')
expect(Cypress.$Cypress.$.expr[':'].contains).to.eq(contains)
})
})
@@ -1316,6 +1213,20 @@ describe('src/cy/commands/querying', () => {
cy.contains(/=[0-6]/, { timeout: 100 }).should('have.text', 'a=2')
})
it('does not interfere with other aliased .contains()', () => {
/*
* There was a regression (no github issue logged) while refactoring .contains() where if a test aliased
* a query using .contains(), future .contains() calls could overwrite its internal state, causing the first one
* to look for the second one's arguments rather than its own.
*
* This test guards against that regression; if the `contains('New York')` inside @newYork alias were
* overwritten by contains(`Nested Find`), then the existence assertion would fail.
*/
cy.contains('New York').as('newYork')
cy.contains('Nested Find').invoke('remove')
cy.get('@newYork').should('exist')
})
describe('should(\'not.exist\')', () => {
it('returns null when no content exists', () => {
cy.contains('alksjdflkasjdflkajsdf').should('not.exist').then(($el) => {
@@ -1422,6 +1333,7 @@ space
it('is case sensitive when matchCase is undefined', () => {
cy.get('#test-button').contains('Test')
cy.contains('test').should('not.exist')
})
it('is case sensitive when matchCase is true', () => {
@@ -1599,16 +1511,20 @@ space
})
describe('special characters', () => {
_.each('\' " [ ] { } . @ # $ % ^ & * ( ) , ; :'.split(' '), (char) => {
it(`finds content by string with character: ${char}`, () => {
const specialCharacters = '\' " [ ] { } . @ # $ % ^ & * ( ) , ; :'.split(' ')
it(`finds content by string with characters`, () => {
_.each(specialCharacters, (char) => {
const span = $(`<span>special char ${char} content</span>`).appendTo(cy.$$('body'))
cy.contains('span', char).then(($span) => {
expect($span.get(0)).to.eq(span.get(0))
})
})
})
it(`finds content by regex with character: ${char}`, () => {
it(`finds content by regex with characters`, () => {
_.each(specialCharacters, (char) => {
const span = $(`<span>special char ${char} content</span>`).appendTo(cy.$$('body'))
cy.contains('span', new RegExp(_.escapeRegExp(char))).then(($span) => {
@@ -1676,22 +1592,16 @@ space
})
})
it('sets type to parent when subject isnt element', () => {
cy.window().contains('foo').then(function () {
expect(this.lastLog.get('type')).to.eq('parent')
it('sets type to child when used as a child command', () => {
cy.get('#specific-contains').contains('foo').then(function () {
expect(this.lastLog.get('type')).to.eq('child')
cy.document().contains('foo').then(function () {
expect(this.lastLog.get('type')).to.eq('parent')
expect(this.lastLog.get('type')).to.eq('child')
})
})
})
it('sets type to child when used as a child command', () => {
cy.get('body').contains('foo').then(function () {
expect(this.lastLog.get('type')).to.eq('child')
})
})
it('logs when not exists', () => {
cy.contains('does-not-exist').should('not.exist').then(function () {
expect(this.lastLog.get('message')).to.eq('does-not-exist')
@@ -1883,7 +1793,7 @@ space
const containsLog = this.logs[0]
const assertionLog = this.logs[1]
expect(err.message).to.eq('`cy.contains()` cannot be passed a `length` option because it will only ever return 1 element.')
expect(err.message).to.eq('`cy.contains()` only ever returns one element, so you cannot assert on a `length` greater than one.')
expect(err.docsUrl).to.eq('https://on.cypress.io/contains')
expect(containsLog.get('state')).to.eq('passed')
@@ -1897,46 +1807,6 @@ space
cy.contains('Nested Find').should('have.length', 2)
})
it('restores contains even when cy.get fails', (done) => {
const { contains } = Cypress.$Cypress.$.expr[':']
const cyNow = cy.now
cy.on('fail', (err) => {
expect(err.message).to.include('Syntax error, unrecognized expression')
expect(Cypress.$Cypress.$.expr[':'].contains).to.eq(contains)
done()
})
cy.stub(cy, 'now').callsFake(() => cyNow('get', 'aBad:jQuery^Selector', {}))
cy.contains(/^asdf \d+/)
})
it('restores contains on abort', (done) => {
cy.timeout(1000)
const { contains } = Cypress.$Cypress.$.expr[':']
cy.stub(Cypress.runner, 'stop')
cy.on('stop', () => {
_.delay(() => {
expect(Cypress.$Cypress.$.expr[':'].contains).to.eq(contains)
done()
}
, 50)
})
cy.on('command:retry', _.after(2, () => {
Cypress.stop()
}))
cy.contains(/^does not contain asdfasdf at all$/)
})
})
})
})
@@ -1,5 +1,3 @@
const helpers = require('../../../support/helpers')
const { _ } = Cypress
describe('src/cy/commands/querying - shadow dom', () => {
@@ -218,7 +216,7 @@ describe('src/cy/commands/querying - shadow dom', () => {
cy.get('#shadow-element-1').shadow()
.then(function ($el) {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
'Applied To': helpers.getFirstSubjectByName('get').get(0),
'Applied To': cy.$$('#shadow-element-1')[0],
Yielded: Cypress.dom.getElements($el),
Elements: $el.length,
Command: 'shadow',
@@ -104,7 +104,7 @@ describe('src/cy/commands/querying/within', () => {
})
})
it('clears withinSubject after within is over', () => {
it('clears withinSubjectChain after within is over', () => {
const input = cy.$$('input:first')
const span = cy.$$('#button-text button span')
@@ -133,17 +133,17 @@ describe('src/cy/commands/querying/within', () => {
})
})
it('clears withinSubject even if next is null', (done) => {
it('clears withinSubjectChain even if next is null', (done) => {
const span = cy.$$('#button-text button span')
// should be defined here because next would have been
// null and withinSubject would not have been cleared
// null and withinSubjectChain would not have been cleared
cy.once('command:queue:before:end', () => {
expect(cy.state('withinSubject')).not.to.be.undefined
expect(cy.state('withinSubjectChain')).not.to.be.undefined
})
cy.once('command:queue:end', () => {
expect(cy.state('withinSubject')).to.be.null
expect(cy.state('withinSubjectChain')).to.be.null
done()
})
@@ -169,6 +169,16 @@ describe('src/cy/commands/querying/within', () => {
cy.contains(`button`, `button`).should(`exist`)
})
it('re-queries if withinSubject is detached from dom', () => {
cy.on('command:retry', _.after(2, (options) => {
cy.$$('#wrapper').replaceWith('<div id="wrapper"><div id="upper">Newer York</div></div>')
}))
cy.get('#wrapper').within(() => {
cy.get(`#upper`).should(`contain.text`, `Newer York`)
})
})
describe('.log', () => {
beforeEach(function () {
this.logs = []
@@ -219,7 +229,7 @@ describe('src/cy/commands/querying/within', () => {
it('provides additional information in console prop', () => {
cy.get('div').within(() => {})
.then(function () {
cy.then(function () {
const { lastLog } = this
const consoleProps = lastLog.get('consoleProps')()
@@ -287,7 +297,7 @@ describe('src/cy/commands/querying/within', () => {
})
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.within()` failed because this element')
expect(err.message).to.include('`cy.within()` failed because it requires a DOM element')
done()
})
@@ -0,0 +1,64 @@
context('cy.server', () => {
it('throws error on use of cy.server', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.server()` was removed in Cypress version 12.0.0. Please update to use `cy.intercept()` instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/intercept')
done()
})
cy.server()
})
})
context('cy.route', () => {
it('throws error on use of cy.route', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.route()` was removed in Cypress version 12.0.0. Please update to use `cy.intercept()` instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/intercept')
done()
})
cy.route('/foo')
})
})
context('Cypress.server.defaults', () => {
it('throws error on use of Cypress.Server.defaults', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`Cypress.Server.defaults()` was removed in Cypress version 12.0.0. Please update to use `cy.intercept()` instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/intercept')
done()
})
Cypress.Server.defaults({})
})
})
context('Cypress.Cookies.defaults', () => {
it('throws error on use of Cookies.defaults()', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`Cypress.Cookies.defaults()` was removed in Cypress version 12.0.0. Please update to use `cy.session()` instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/session')
done()
})
Cypress.Cookies.defaults({})
})
})
context('Cypress.Cookies.preserveOnce', () => {
it('throws error on use of Cookies.preserveOnce', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`Cypress.Cookies.preserveOnce()` was removed in Cypress version 12.0.0. Please update to use `cy.session()` instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/session')
done()
})
Cypress.Cookies.preserveOnce({})
})
})
@@ -366,9 +366,9 @@ describe('src/cy/commands/request', () => {
},
})
.then(function () {
// qs is encoded and merged into the url to make url consistent with cy.visit() and URLSearchParams
this.expectOptionsToBe({
url: 'http://localhost:8888/',
qs: { foo: 'bar' },
url: 'http://localhost:8888/?foo=bar',
})
})
})
@@ -432,6 +432,53 @@ describe('src/cy/commands/request', () => {
})
})
describe('querystring', () => {
// https://github.com/cypress-io/cypress/issues/19407
it('generated querystring should be consistent with cy.visit() and URLSearchParams', () => {
const url = '/status-code'
const qs = {
postId: [1, 2],
id: 3,
}
let visitURL
let requestURL
cy.intercept(`${url}*`, {
statusCode: 200,
headers: {
'content-type': 'text/html',
},
}).as('request')
cy.visit({
url,
qs,
})
cy.wait('@request').then(({ request }) => {
visitURL = request.url
}).then(() => {
cy.request({
url,
qs,
}).then((resp) => {
requestURL = resp.allRequestResponses[0]['Request URL']
})
}).then(() => {
expect(requestURL).to.eq(visitURL)
// Check if the querystring matches the querystring generated by the default URLSearchParams.
const params = new URLSearchParams({
postId: [1, 2],
id: 3,
})
expect(requestURL).to.eq(`http://localhost:3500${url}?${params.toString()}`)
})
})
})
describe('failOnStatusCode', () => {
it('does not fail on status 401', () => {
Cypress.backend
@@ -52,7 +52,7 @@ describe('cy.session', { retries: 0 }, () => {
})
})
describe('testIsolation=on', { testIsolation: 'on' }, () => {
describe('testIsolation=true', { testIsolation: true }, () => {
describe('test:before:run:async', () => {
it('clears page before each run', () => {
cy.visit('/fixtures/form.html')
@@ -62,9 +62,10 @@ describe('cy.session', { retries: 0 }, () => {
await Cypress.action('runner:test:before:run:async', {})
expect(Cypress.action).to.be.calledWith('cy:url:changed', '')
expect(Cypress.action).to.be.calledWith('cy:visit:blank', { type: 'session-lifecycle' })
expect(Cypress.action).to.be.calledWith('cy:visit:blank', { testIsolation: true })
})
.url('about:blank')
.url()
.should('eq', 'about:blank')
})
it('clears session data before each run', async () => {
@@ -96,7 +97,7 @@ describe('cy.session', { retries: 0 }, () => {
await Cypress.action('runner:test:before:run:async', {})
expect(Cypress.action).to.be.calledWith('cy:url:changed', '')
expect(Cypress.action).to.be.calledWith('cy:visit:blank', { type: 'session-lifecycle' })
expect(Cypress.action).to.be.calledWith('cy:visit:blank', { testIsolation: true })
})
cy.window().its('cookie').should('be.undefined')
@@ -775,7 +776,7 @@ describe('cy.session', { retries: 0 }, () => {
})
})
describe('testIsolation=off', { testIsolation: 'off' }, () => {
describe('testIsolation=false', { testIsolation: false }, () => {
before(async () => {
// manually ensure clear browser state! since we turned testIsolation off
await Cypress.session.clearCurrentSessionData()
@@ -1459,50 +1460,6 @@ describe('cy.session', { retries: 0 }, () => {
return null
})
it('throws error when experimentalSessionAndOrigin not enabled', { experimentalSessionAndOrigin: false, experimentalSessionSupport: false }, (done) => {
cy.once('fail', (err) => {
expect(lastSessionLog).to.eq(lastLog)
expect(lastSessionLog.get('error')).to.eq(err)
expect(lastSessionLog.get('state')).to.eq('failed')
expect(err.message).to.eq('`cy.session()` requires enabling the `experimentalSessionAndOrigin` flag.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')
done()
})
cy.session('sessions-not-enabled')
})
it('throws error when experimentalSessionSupport is enabled through test config', { experimentalSessionAndOrigin: false, experimentalSessionSupport: true }, (done) => {
cy.once('fail', (err) => {
expect(lastSessionLog).to.eq(lastLog)
expect(lastSessionLog.get('error')).to.eq(err)
expect(lastSessionLog.get('state')).to.eq('failed')
expect(err.message).to.eq('\`cy.session()\` requires enabling the \`experimentalSessionAndOrigin\` flag. The \`experimentalSessionSupport\` flag was enabled but was removed in Cypress version 9.6.0.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')
done()
})
cy.session('sessions-not-enabled')
})
it('throws error when experimentalSessionSupport is enabled through Cypress.config', { experimentalSessionAndOrigin: false }, (done) => {
Cypress.config('experimentalSessionSupport', true)
cy.once('fail', (err) => {
Cypress.config('experimentalSessionSupport', false)
expect(lastSessionLog).to.eq(lastLog)
expect(lastLog.get('error')).to.eq(err)
expect(lastLog.get('state')).to.eq('failed')
expect(err.message).to.eq('\`cy.session()\` requires enabling the \`experimentalSessionAndOrigin\` flag. The \`experimentalSessionSupport\` flag was enabled but was removed in Cypress version 9.6.0.')
expect(err.docsUrl).to.eq('https://on.cypress.io/session')
done()
})
cy.session('sessions-not-enabled')
})
it('throws when sessionId argument was not provided', function (done) {
cy.once('fail', (err) => {
expect(lastSessionLog).to.eq(lastLog)
@@ -176,7 +176,7 @@ describe('src/cy/commands/sessions/utils.ts', () => {
})
describe('.navigateAboutBlank', () => {
it('triggers session blank page visit', () => {
it('triggers test isolation blank page visit', () => {
const stub = cy.stub(Cypress, 'action').log(false)
.callThrough()
.withArgs('cy:visit:blank')
@@ -185,19 +185,8 @@ describe('src/cy/commands/sessions/utils.ts', () => {
navigateAboutBlank()
navigateAboutBlank(true)
expect(stub).to.have.been.calledTwice
expect(stub.args[0]).to.deep.eq(['cy:visit:blank', { type: 'session' }])
expect(stub.args[1]).to.deep.eq(['cy:visit:blank', { type: 'session' }])
})
})
it('triggers session-lifecycle blank page visit', () => {
const stub = cy.stub(Cypress, 'action').log(false)
.callThrough()
.withArgs('cy:visit:blank')
cy.then(() => {
navigateAboutBlank(false)
expect(stub).to.have.been.calledWith('cy:visit:blank', { type: 'session-lifecycle' })
expect(stub.args[0]).to.deep.eq(['cy:visit:blank', { testIsolation: true }])
expect(stub.args[1]).to.deep.eq(['cy:visit:blank', { testIsolation: true }])
})
})
})
@@ -246,11 +246,7 @@ describe('src/cy/commands/storage', () => {
const clear = cy.spy(Cypress.LocalStorage, 'clear')
Cypress.emit('test:before:run', {})
if (Cypress.config('experimentalSessionAndOrigin')) {
expect(clear).not.to.be.called
} else {
expect(clear).to.be.calledWith([])
}
expect(clear).not.to.be.called
})
})
@@ -1,8 +1,6 @@
const { assertLogLength } = require('../../support/utils')
const { _, $, dom } = Cypress
const helpers = require('../../support/helpers')
describe('src/cy/commands/traversals', () => {
beforeEach(() => {
cy.visit('/fixtures/dom.html')
@@ -95,7 +93,7 @@ describe('src/cy/commands/traversals', () => {
})
cy.on('fail', (err) => {
expect(err.message).to.include(`\`cy.${name}()\` failed because this element`)
expect(err.message).to.include(`\`cy.${name}()\` failed because it requires a DOM element`)
done()
})
@@ -108,7 +106,7 @@ describe('src/cy/commands/traversals', () => {
node = dom.stringify(cy.$$(node), 'short')
cy.on('fail', (err) => {
expect(err.message).to.include(`Expected to find element: \`${el}\`, but never found it. Queried from element: ${node}`)
expect(err.message).to.include(`Expected to find element: \`${el}\`, but never found it. Queried from:`)
done()
})
@@ -208,7 +206,7 @@ describe('src/cy/commands/traversals', () => {
const yielded = Cypress.dom.getElements($el)
_.extend(obj, {
'Applied To': helpers.getFirstSubjectByName('get').get(0),
'Applied To': cy.$$('#list')[0],
Yielded: yielded,
Elements: $el.length,
})
@@ -324,7 +322,7 @@ describe('src/cy/commands/traversals', () => {
it('errors after timing out not finding element', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('Expected to find element: `span`, but never found it. Queried from element: <li.item>')
expect(err.message).to.include('Expected to find element: `span`, but never found it. Queried from:')
done()
})
+142 -151
View File
@@ -61,15 +61,14 @@ describe('src/cy/commands/waiting', () => {
const response = { foo: 'foo' }
cy
.server()
.route('GET', /.*/, response).as('fetch')
.intercept('GET', /.*/, response).as('fetch')
.window().then((win) => {
xhrGet(win, '/foo')
return null
})
.wait('@fetch.response').then((xhr) => {
expect(xhr.responseBody).to.deep.eq(response)
expect(xhr.response.body).to.deep.eq(response)
})
})
@@ -83,11 +82,13 @@ describe('src/cy/commands/waiting', () => {
}))
cy
.server({ delay: 1000 })
.route(/users/, {}).as('getUsers')
.intercept(/users/, {
body: {},
delay: 1000,
}).as('getUsers')
.wait('@getUsers.request').then((xhr) => {
expect(xhr.url).to.include('/users')
expect(xhr.response).to.be.null
expect(xhr.request.url).to.include('/users')
expect(xhr.response).to.be.undefined
})
})
@@ -99,8 +100,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('GET', /.*/, {}).as('fetch')
.intercept('GET', /.*/, {}).as('fetch')
.wait('@fetch', { timeout: 900 })
})
@@ -114,8 +114,7 @@ describe('src/cy/commands/waiting', () => {
cy.on('command:retry', retry)
cy
.server()
.route('GET', /.*/, {}).as('fetch')
.intercept('GET', /.*/, {}).as('fetch')
.wait('@fetch').then(() => {
expect(cy.timeout()).to.eq(prevTimeout)
})
@@ -131,8 +130,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {}).as('fetch')
.wait('@fetch').then(() => {
expect(cy.timeout()).to.eq(199)
})
@@ -147,8 +145,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {}).as('fetch')
.wait('@fetch', { requestTimeout: 199 })
})
@@ -156,14 +153,18 @@ describe('src/cy/commands/waiting', () => {
responseTimeout: 299,
}, (done) => {
cy.on('command:retry', (options) => {
expect(options.timeout).to.eq(299)
if (options.type === 'response') {
expect(options.timeout).to.eq(299)
done()
done()
}
})
cy
.server({ delay: 100 })
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {
body: {},
delay: 100,
}).as('fetch')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -174,15 +175,19 @@ describe('src/cy/commands/waiting', () => {
it('waits for responseTimeout override', (done) => {
cy.on('command:retry', (options) => {
expect(options.type).to.eq('response')
expect(options.timeout).to.eq(299)
if (options.type === 'response') {
expect(options.type).to.eq('response')
expect(options.timeout).to.eq(299)
done()
done()
}
})
cy
.server({ delay: 100 })
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {
body: {},
delay: 100,
}).as('fetch')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -198,7 +203,7 @@ describe('src/cy/commands/waiting', () => {
retryCount++
if (retryCount === 1) {
expect(options.type).to.eq('request')
expect(options.timeout).to.eq(100)
expect(options.timeout).to.eq(1000)
// trigger request to move onto response timeout verification
const win = cy.state('window')
@@ -206,26 +211,27 @@ describe('src/cy/commands/waiting', () => {
xhrGet(win, '/foo')
}
if (retryCount === 2) {
if (options.type === 'response') {
expect(options.type).to.eq('response')
expect(options.timeout).to.eq(299)
expect(options.timeout).to.eq(1001)
done()
}
})
cy
.server({ delay: 100 })
.route('GET', '*', {}).as('fetch')
.wait('@fetch', { requestTimeout: 100, responseTimeout: 299 })
.intercept('GET', '*', {
body: {},
delay: 100,
}).as('fetch')
.wait('@fetch', { requestTimeout: 1000, responseTimeout: 1001 })
})
// https://github.com/cypress-io/cypress/issues/369
it('does not mutate 2nd route methods when using shorthand route', () => {
cy
.server()
.route('POST', /foo/, {}).as('getFoo')
.route(/bar/, {}).as('getBar')
.intercept('POST', /foo/, {}).as('getFoo')
.intercept(/bar/, {}).as('getBar')
.window().then((win) => {
win.$.post('/foo')
xhrGet(win, '/bar')
@@ -269,8 +275,7 @@ describe('src/cy/commands/waiting', () => {
done()
})
cy.server()
cy.route('GET', /.*/, {}).as('fetch')
cy.intercept('GET', /.*/, {}).as('fetch')
cy.wait('@fetch')
})
@@ -284,8 +289,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, {}).as('foo')
.intercept(/foo/, {}).as('foo')
.wait('@foo.request')
})
@@ -297,8 +301,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('*', {}).as('getAny')
.intercept('*', {}).as('getAny')
.wait('getAny').then(() => {})
})
@@ -310,8 +313,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, {}).as('foo')
.intercept(/foo/, {}).as('foo')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -328,9 +330,8 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, {}).as('foo')
.route(/bar/, {}).as('bar')
.intercept(/foo/, {}).as('foo')
.intercept(/bar/, {}).as('bar')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -348,8 +349,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, {}).as('foo')
.intercept(/foo/, {}).as('foo')
.get('body').as('bar')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -376,9 +376,8 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, {}).as('foo')
.route(/bar/, {}).as('bar')
.intercept(/foo/, {}).as('foo')
.intercept(/bar/, {}).as('bar')
.wait(['@foo', '@bar'])
})
@@ -400,9 +399,8 @@ describe('src/cy/commands/waiting', () => {
}))
cy
.server()
.route(/foo/, { foo: 'foo' }).as('foo')
.route(/bar/, { bar: 'bar' }).as('bar')
.intercept(/foo/, { foo: 'foo' }).as('foo')
.intercept(/bar/, { bar: 'bar' }).as('bar')
.wait(['@foo', '@bar'])
})
@@ -424,9 +422,8 @@ describe('src/cy/commands/waiting', () => {
}))
cy
.server()
.route(/foo/, { foo: 'foo' }).as('foo')
.route(/bar/, { bar: 'bar' }).as('bar')
.intercept(/foo/, { foo: 'foo' }).as('foo')
.intercept(/bar/, { bar: 'bar' }).as('bar')
.wait(['@foo', '@bar'])
})
@@ -442,9 +439,8 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, {}).as('foo')
.route(/bar/, {}).as('bar')
.intercept(/foo/, {}).as('foo')
.intercept(/bar/, {}).as('bar')
.wait(['@foo', 'bar'])
})
@@ -461,8 +457,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, {}).as('foo')
.intercept(/foo/, {}).as('foo')
.get('body').as('bar')
.wait(['@foo', '@bar'])
})
@@ -484,9 +479,8 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/foo/, { foo: 'foo' }).as('foo')
.route(/bar/, { bar: 'bar' }).as('bar')
.intercept(/foo/, { foo: 'foo' }).as('foo')
.intercept(/bar/, { bar: 'bar' }).as('bar')
.wait(['@foo', '@bar'])
})
@@ -515,8 +509,7 @@ describe('src/cy/commands/waiting', () => {
return xhrGet(win, `/users?num=${response}`)
})
cy.server()
cy.route(/users/, resp).as('get.users')
cy.intercept(/users/, resp).as('get.users')
cy.wait(['@get.users', '@get.users', '@get.users'])
})
@@ -541,8 +534,7 @@ describe('src/cy/commands/waiting', () => {
}))
cy
.server()
.route(/users/, resp).as('getUsers')
.intercept(/users/, resp).as('getUsers')
.wait('@getUsers')
.wait('@getUsers')
})
@@ -568,8 +560,7 @@ describe('src/cy/commands/waiting', () => {
}))
cy
.server()
.route(/users/, resp).as('getUsers')
.intercept(/users/, resp).as('getUsers')
.wait('@getUsers.request')
.wait('@getUsers.request')
})
@@ -584,8 +575,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('*').as('response')
.intercept('*').as('response')
.window().then((win) => {
xhrGet(win, '/timeout?ms=500')
@@ -604,8 +594,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('*').as('response')
.intercept('*').as('response')
.window().then((win) => {
xhrGet(win, '/timeout?ms=0')
xhrGet(win, '/timeout?ms=5000')
@@ -625,9 +614,8 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('/timeout?ms=0').as('foo')
.route('/timeout?ms=5000').as('bar')
.intercept('/timeout?ms=0').as('foo')
.intercept('/timeout?ms=5000').as('bar')
.window().then((win) => {
xhrGet(win, '/timeout?ms=0')
xhrGet(win, '/timeout?ms=5000')
@@ -655,11 +643,13 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server({ delay: 200 })
.route(/users/, {}).as('getUsers')
.intercept(/users/, {
body: {},
delay: 200,
}).as('getUsers')
.wait('@getUsers.request').then((xhr) => {
expect(xhr.url).to.include('/users')
expect(xhr.response).to.be.null
expect(xhr.request.url).to.include('/users')
expect(xhr.response).to.be.undefined
})
.wait('@getUsers')
})
@@ -686,10 +676,9 @@ describe('src/cy/commands/waiting', () => {
done()
})
cy.server()
cy.route('/timeout?ms=2001').as('getOne')
cy.route('/timeout?ms=2002').as('getTwo')
cy.route(/three/, {}).as('get.three')
cy.intercept('/timeout?ms=2001').as('getOne')
cy.intercept('/timeout?ms=2002').as('getTwo')
cy.intercept(/three/, {}).as('get.three')
cy.wait(['@getOne', '@getTwo', '@get.three'])
})
@@ -700,7 +689,7 @@ describe('src/cy/commands/waiting', () => {
const win = cy.state('window')
cy.on('command:retry', (options) => {
if (/getThree/.test(options.error)) {
if (/getThree/.test(options.error) && options.type === 'response') {
options._runnableTimeout = 0
}
})
@@ -712,10 +701,9 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('/timeout?ms=1').as('getOne')
.route('/timeout?ms=2').as('getTwo')
.route('/timeout?ms=3000').as('getThree')
.intercept('/timeout?ms=1').as('getOne')
.intercept('/timeout?ms=2').as('getTwo')
.intercept('/timeout?ms=3000').as('getThree')
.then(() => {
xhrGet(win, '/timeout?ms=1')
xhrGet(win, '/timeout?ms=2')
@@ -758,9 +746,8 @@ describe('src/cy/commands/waiting', () => {
const resp1 = { foo: 'foo' }
const resp2 = { bar: 'bar' }
cy.server()
cy.route(/users/, resp1).as('getUsers')
cy.route(/posts/, resp2).as('get.posts')
cy.intercept(/users/, resp1).as('getUsers')
cy.intercept(/posts/, resp2).as('get.posts')
cy.window().then((win) => {
xhrGet(win, '/users')
xhrGet(win, '/posts')
@@ -769,8 +756,8 @@ describe('src/cy/commands/waiting', () => {
})
cy.wait(['@getUsers', '@get.posts']).spread((xhr1, xhr2) => {
expect(xhr1.responseBody).to.deep.eq(resp1)
expect(xhr2.responseBody).to.deep.eq(resp2)
expect(xhr1.response.body).to.deep.eq(resp1)
expect(xhr2.response.body).to.deep.eq(resp2)
})
})
})
@@ -795,19 +782,18 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/users/, resp).as('getUsers')
.intercept(/users/, resp).as('getUsers')
.wait('@getUsers').then((xhr) => {
expect(xhr.url).to.include('/users?num=1')
expect(xhr.responseBody).to.deep.eq(resp)
expect(xhr.request.url).to.include('/users?num=1')
expect(xhr.response.body).to.deep.eq(resp)
})
.wait('@getUsers').then((xhr) => {
expect(xhr.url).to.include('/users?num=2')
expect(xhr.responseBody).to.deep.eq(resp)
expect(xhr.request.url).to.include('/users?num=2')
expect(xhr.response.body).to.deep.eq(resp)
})
.wait('@getUsers').then((xhr) => {
expect(xhr.url).to.include('/users?num=3')
expect(xhr.responseBody).to.deep.eq(resp)
expect(xhr.request.url).to.include('/users?num=3')
expect(xhr.response.body).to.deep.eq(resp)
})
})
@@ -829,17 +815,16 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route(/users/, resp).as('getUsers')
.intercept(/users/, resp).as('getUsers')
.wait(['@getUsers', '@getUsers', '@getUsers']).spread((xhr1, xhr2, xhr3) => {
expect(xhr1.url).to.include('/users?num=1')
expect(xhr1.request.url).to.include('/users?num=1')
expect(xhr2.url).to.include('/users?num=2')
expect(xhr3.url).to.include('/users?num=3')
expect(xhr3.request.url).to.include('/users?num=3')
}).wait('@getUsers').then((xhr) => {
expect(xhr.url).to.include('/users?num=4')
expect(xhr.request.url).to.include('/users?num=4')
expect(xhr.responseBody).to.deep.eq(resp)
expect(xhr.response.body).to.deep.eq(resp)
})
})
@@ -881,6 +866,10 @@ describe('src/cy/commands/waiting', () => {
cy.on('log:added', (attrs, log) => {
this.lastLog = log
if (log.get('name') === 'wait') {
this.lastWaitLog = log
}
this.logs.push(log)
})
@@ -994,8 +983,7 @@ describe('src/cy/commands/waiting', () => {
done()
})
cy.server()
cy.route(/foo/, {}).as('getFoo')
cy.intercept(/foo/, {}).as('getFoo')
cy.noop({}).wait('@getFoo')
})
@@ -1014,9 +1002,9 @@ describe('src/cy/commands/waiting', () => {
requestTimeout: 100,
}, function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
const { lastWaitLog } = this
expect(lastLog.get('error')).to.eq(err)
expect(lastWaitLog.get('error')).to.eq(err)
expect(err.message).to.include('`cy.wait()` timed out waiting `100ms` for the 1st request to the route: `getBar`. No request ever occurred.')
done()
@@ -1025,9 +1013,8 @@ describe('src/cy/commands/waiting', () => {
cy.visit('/fixtures/empty.html')
cy
.server()
.route(/foo/, {}).as('getFoo')
.route(/bar/, {}).as('getBar')
.intercept(/foo/, {}).as('getFoo')
.intercept(/bar/, {}).as('getBar')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -1050,8 +1037,7 @@ describe('src/cy/commands/waiting', () => {
it('is a parent command', () => {
cy
.server()
.route(/foo/, {}).as('getFoo')
.intercept(/foo/, {}).as('getFoo')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -1066,9 +1052,8 @@ describe('src/cy/commands/waiting', () => {
it('passes as array of referencesAlias', () => {
cy
.server()
.route(/foo/, {}).as('getFoo')
.route(/bar/, {}).as('getBar')
.intercept(/foo/, {}).as('getFoo')
.intercept(/bar/, {}).as('getBar')
.window().then((win) => {
xhrGet(win, '/foo')
xhrGet(win, '/bar')
@@ -1077,9 +1062,9 @@ describe('src/cy/commands/waiting', () => {
return null
})
.wait(['@getFoo', '@getBar', '@getFoo']).then(function (xhrs) {
const { lastLog } = this
const { lastWaitLog } = this
expect(lastLog.get('referencesAlias')).to.deep.eq([
expect(lastWaitLog.get('referencesAlias')).to.deep.eq([
{
name: 'getFoo',
cardinal: 1,
@@ -1101,15 +1086,14 @@ describe('src/cy/commands/waiting', () => {
it('#consoleProps waiting on 1 alias', () => {
cy
.server()
.route(/foo/, {}).as('getFoo')
.intercept(/foo/, {}).as('getFoo')
.window().then((win) => {
xhrGet(win, '/foo')
return null
})
.wait('@getFoo').then(function (xhr) {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
expect(this.lastWaitLog.invoke('consoleProps')).to.deep.eq({
Command: 'wait',
'Waited For': 'getFoo',
Yielded: xhr,
@@ -1119,9 +1103,8 @@ describe('src/cy/commands/waiting', () => {
it('#consoleProps waiting on multiple aliases', () => {
cy
.server()
.route(/foo/, {}).as('getFoo')
.route(/bar/, {}).as('getBar')
.intercept(/foo/, {}).as('getFoo')
.intercept(/bar/, {}).as('getBar')
.window().then((win) => {
xhrGet(win, '/foo')
xhrGet(win, '/bar')
@@ -1129,7 +1112,7 @@ describe('src/cy/commands/waiting', () => {
return null
})
.wait(['@getFoo', '@getBar']).then(function (xhrs) {
expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
expect(this.lastWaitLog.invoke('consoleProps')).to.deep.eq({
Command: 'wait',
'Waited For': 'getFoo, getBar',
Yielded: [xhrs[0], xhrs[1]], // explicitly create the array here
@@ -1153,8 +1136,7 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {}).as('fetch')
.wait('@fetch')
})
@@ -1166,23 +1148,26 @@ describe('src/cy/commands/waiting', () => {
})
cy
.server()
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {}).as('fetch')
.wait('@fetch', { requestTimeout: 199 })
})
it('sets default responseTimeout', {
responseTimeout: 299,
}, function (done) {
cy.on('command:retry', () => {
expect(this.lastLog.get('timeout')).to.eq(299)
cy.on('command:retry', (command) => {
if (command.type === 'response') {
expect(this.lastWaitLog.get('timeout')).to.eq(299)
done()
done()
}
})
cy
.server({ delay: 100 })
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {
body: {},
delay: 100,
}).as('fetch')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -1192,15 +1177,19 @@ describe('src/cy/commands/waiting', () => {
})
it('sets custom responseTimeout', function (done) {
cy.on('command:retry', () => {
expect(this.lastLog.get('timeout')).to.eq(299)
cy.on('command:retry', (command) => {
if (command.type === 'response') {
expect(this.lastWaitLog.get('timeout')).to.eq(299)
done()
done()
}
})
cy
.server({ delay: 100 })
.route('GET', '*', {}).as('fetch')
.intercept('GET', '*', {
body: {},
delay: 100,
}).as('fetch')
.window().then((win) => {
xhrGet(win, '/foo')
@@ -1213,11 +1202,11 @@ describe('src/cy/commands/waiting', () => {
let log
let retryCount = 0
cy.on('command:retry', () => {
log = log || this.lastLog
cy.on('command:retry', (command) => {
log = log || this.lastWaitLog
retryCount++
if (retryCount === 1) {
expect(log.get('timeout')).to.eq(100)
expect(log.get('timeout')).to.eq(1000)
// trigger request to move onto response timeout verification
const win = cy.state('window')
@@ -1225,17 +1214,19 @@ describe('src/cy/commands/waiting', () => {
xhrGet(win, '/foo')
}
if (retryCount === 2) {
expect(log.get('timeout')).to.eq(299)
if (command.type === 'response') {
expect(log.get('timeout')).to.eq(1001)
done()
}
})
cy
.server({ delay: 100 })
.route('GET', '*', {}).as('fetch')
.wait('@fetch', { requestTimeout: 100, responseTimeout: 299 })
.intercept('GET', '*', {
body: {},
delay: 100,
}).as('fetch')
.wait('@fetch', { requestTimeout: 1000, responseTimeout: 1001 })
})
})
})
File diff suppressed because it is too large Load Diff
@@ -11,7 +11,6 @@ const createCommand = (props = {}) => {
type: 'parent',
chainerId: _.uniqueId('ch'),
userInvocationStack: '',
injected: false,
fn () {},
}, props))
}
@@ -25,15 +24,17 @@ const log = (props = {}) => {
describe('src/cypress/command_queue', () => {
let queue
const state = (() => {}) as StateFunc
const timeout = () => {}
const whenStable = {} as IStability
const fail = () => {}
const isCy = () => true
const clearTimeout = () => {}
const setSubjectForChainer = () => {}
const stubCy = {
timeout: () => {},
fail: () => {},
isCy: () => true,
clearTimeout: () => {},
setSubjectForChainer: () => {},
}
beforeEach(() => {
queue = new CommandQueue(state, timeout, whenStable, fail, isCy, clearTimeout, setSubjectForChainer)
queue = new CommandQueue(state, whenStable, stubCy as any)
queue.add(createCommand({
name: 'get',
+98 -25
View File
@@ -142,7 +142,7 @@ describe('driver/src/cypress/cy', () => {
cy.wrap(subject).then(() => {
expect(cy.state('subject')).to.equal(subject)
expect(Cypress.utils.warning).to.be.calledWith('`cy.state(\'subject\')` has been deprecated and will be removed in a future release. Consider migrating to `cy.currentSubject()` instead.')
expect(Cypress.utils.warning).to.be.calledWith('`cy.state(\'subject\')` has been deprecated and will be removed in a future release. Consider migrating to `cy.subject()` instead.')
})
})
})
@@ -300,23 +300,6 @@ describe('driver/src/cypress/cy', () => {
cy.c('bar')
})
it('fails when previous subject becomes detached', (done) => {
cy.$$('#button').click(function () {
return $(this).remove()
})
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.parent()` failed because this element is detached from the DOM.')
expect(err.message).to.include('<button id="button">button</button>')
expect(err.message).to.include('> `cy.click()`')
expect(err.docsUrl).to.eq('https://on.cypress.io/element-has-detached-from-dom')
done()
})
cy.get('#button').click().parent()
})
it('fails when previous subject isnt window', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.winOnly()` failed because it requires the subject be a global `window` object.')
@@ -346,10 +329,9 @@ describe('driver/src/cypress/cy', () => {
cy.on('fail', (err) => {
expect(firstPassed).to.be.true
expect(err.message).to.include('`cy.elWinOnly()` failed because it requires a DOM element.')
expect(err.message).to.include('`cy.elWinOnly()` failed because it requires a DOM element or window.')
expect(err.message).to.include('string')
expect(err.message).to.include('> `cy.wrap()`')
expect(err.message).to.include('All 2 subject validations failed')
done()
})
@@ -416,10 +398,10 @@ describe('driver/src/cypress/cy', () => {
return orig(`foo${arg1}`)
})
Cypress.Commands.overwrite('first', (orig, subject) => {
subject = $([1, 2])
Cypress.Commands.overwrite('each', (orig, subject, cb) => {
subject = $([1])
return orig(subject)
return orig(subject, cb)
})
Cypress.Commands.overwrite('noop', function (orig, fn) {
@@ -439,8 +421,8 @@ describe('driver/src/cypress/cy', () => {
})
it('can modify child commands', () => {
cy.get('li').first().then((el) => {
expect(el[0]).to.eq(1)
cy.get('li').each((i) => {
expect(i).to.eq(1)
})
})
@@ -493,4 +475,95 @@ describe('driver/src/cypress/cy', () => {
cy.bar()
})
})
context('queries', {
defaultCommandTimeout: 30,
}, () => {
it('throws when queries return a promise', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.promiseQuery()` failed because you returned a promise from a query.\n\nQueries must be synchronous functions that return a function. You cannot invoke commands or return promises inside of them.')
done()
})
Cypress.Commands.addQuery('promiseQuery', () => Promise.resolve())
cy.promiseQuery()
})
it('throws when a query returns a non-function value', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.badReturnQuery()` failed because you returned a value other than a function from a query.\n\nQueries must be synchronous functions that return a function.\n\nThe returned value was:\n\n > `1`')
done()
})
Cypress.Commands.addQuery('badReturnQuery', () => 1)
cy.badReturnQuery()
})
it('throws when a command is invoked inside a query', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.commandQuery()` failed because you invoked a command inside a query.\n\nQueries must be synchronous functions that return a function. You cannot invoke commands or return promises inside of them.\n\nThe command invoked was:\n\n > `cy.visit()`')
done()
})
Cypress.Commands.addQuery('commandQuery', () => cy.visit('/'))
cy.commandQuery()
})
it('custom commands that return query chainers retry', () => {
Cypress.Commands.add('getButton', () => cy.get('button'))
cy.on('command:retry', () => cy.$$('button').first().remove())
cy.getButton().should('have.length', 23)
})
it('allows queries to use other queries', () => {
const logs = []
cy.on('log:added', (attrs, log) => logs.push(log))
Cypress.Commands.addQuery('getButtonQuery', () => {
cy.now('get', 'body')
return cy.now('get', 'button')
})
Cypress.Commands.addQuery('queryQuery', () => cy.now('getButtonQuery'))
cy.queryQuery().should('have.length', 24)
cy.then(() => {
// Length of 3: getButtonQuery.body (from get), getButtonQuery.button (from get), `should.have.length.24`
expect(logs.length).to.eq(3)
})
})
it('closes each log as the query completes', (done) => {
let getLog
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'get') {
getLog = log
} else if (attrs.name === 'find') {
expect(getLog.get('state')).to.eq('passed')
done()
}
})
cy.get('body').find('#wrapper')
})
it('ends all messages when query chain fails', (done) => {
const logs = []
cy.on('log:added', (attrs, log) => logs.push(log))
cy.on('fail', (err) => {
const state = logs.map((l) => l.get('state'))
expect(state).to.eql(['passed', 'passed', 'passed', 'failed'])
done()
})
cy.get('body').find('#specific-contains').children().should('have.class', 'active')
})
})
})
@@ -460,61 +460,6 @@ describe('Proxy Logging', () => {
))
})
})
context('with cy.route()', () => {
context('flags', () => {
let $XMLHttpRequest
const testFlagXhr = (expectStatus, expectInterceptions, setupFn) => {
return testFlag(expectStatus, expectInterceptions, setupFn, (url) => {
const xhr = new $XMLHttpRequest()
xhr.open('GET', url)
xhr.send()
})
}
beforeEach(() => {
cy.visit('/fixtures/empty.html')
cy.window()
.then(({ XMLHttpRequest }) => {
$XMLHttpRequest = XMLHttpRequest
})
})
it('is unflagged when not routed', testFlagXhr(
undefined,
[],
() => {},
))
it('spied flagged as expected', testFlagXhr(
undefined,
[{
command: 'route',
alias,
type: 'spy',
}],
() => {
cy.server()
cy.route(`${url}`).as(alias)
},
))
it('stubbed flagged as expected', testFlagXhr(
undefined,
[{
command: 'route',
alias,
type: 'stub',
}],
() => {
cy.server()
cy.route(url, 'stubbed response').as(alias)
},
))
})
})
})
context('Cypress.ProxyLogging', () => {
@@ -1,44 +0,0 @@
import $XHR from '@packages/driver/src/cypress/xml_http_request'
describe('src/cypress/xml_http_request', () => {
let xhr
let $xhr
beforeEach(() => {
xhr = {
id: '1',
url: 'http://example.com',
method: 'GET',
}
$xhr = $XHR.create(xhr)
})
context('._getFixtureError', () => {
it('returns __error property on response body', () => {
$xhr.response = {
body: {
__error: 'Something went wrong',
},
}
const err = $xhr._getFixtureError()
expect(err).to.equal('Something went wrong')
})
it('returns undefined if response does not exist', () => {
const err = $xhr._getFixtureError()
expect(err).to.be.undefined
})
it('returns undefined if response body does not exist', () => {
$xhr.response = {}
const err = $xhr._getFixtureError()
expect(err).to.be.undefined
})
})
})
@@ -104,9 +104,7 @@ describe('basic login', { browser: '!webkit' }, () => {
}, {
validate: () => {
cy.window().then((win) => {
const cypressAuthToken = win.sessionStorage.getItem('cypressAuthToken')
return !!cypressAuthToken
expect(win.sessionStorage.getItem('cypressAuthToken')).to.exist
})
},
})
@@ -195,6 +195,9 @@ context('cy.origin actions', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -111,8 +111,8 @@ context('cy.origin connectors', { browser: '!webkit' }, () => {
expect(consoleProps.Function).to.equal('.text()')
expect(consoleProps.Yielded).to.equal('button')
expect(consoleProps.Subject).to.have.property('tagName').that.equals('BUTTON')
expect(consoleProps.Subject).to.have.property('id').that.equals('button')
expect(consoleProps.Subject[0]).to.have.property('tagName').that.equals('BUTTON')
expect(consoleProps.Subject[0]).to.have.property('id').that.equals('button')
})
})
})
@@ -58,7 +58,7 @@ describe('cy.origin cookies', { browser: '!webkit' }, () => {
expect(logs[0].get('state')).to.eq('failed')
expect(logs[0].get('name')).to.eq('getCookie')
expect(logs[0].get('message')).to.eq('foo')
expect(err.message).to.eq('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
expect(err.message).to.include('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
done()
})
@@ -73,7 +73,7 @@ describe('cy.origin cookies', { browser: '!webkit' }, () => {
expect(logs[0].get('state')).to.eq('failed')
expect(logs[0].get('name')).to.eq('getCookies')
expect(logs[0].get('message')).to.eq('')
expect(err.message).to.eq('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
expect(err.message).to.include('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
done()
})
@@ -88,7 +88,7 @@ describe('cy.origin cookies', { browser: '!webkit' }, () => {
expect(logs[0].get('state')).to.eq('failed')
expect(logs[0].get('name')).to.eq('setCookie')
expect(logs[0].get('message')).to.eq('foo, bar')
expect(err.message).to.eq('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
expect(err.message).to.include('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
done()
})
@@ -103,7 +103,7 @@ describe('cy.origin cookies', { browser: '!webkit' }, () => {
expect(logs[0].get('state')).to.eq('failed')
expect(logs[0].get('name')).to.eq('clearCookie')
expect(logs[0].get('message')).to.eq('foo')
expect(err.message).to.eq('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
expect(err.message).to.include('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
done()
})
@@ -118,7 +118,7 @@ describe('cy.origin cookies', { browser: '!webkit' }, () => {
expect(logs[0].get('state')).to.eq('failed')
expect(logs[0].get('name')).to.eq('clearCookies')
expect(logs[0].get('message')).to.eq('')
expect(err.message).to.eq('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
expect(err.message).to.include('Timed out retrying after 100ms: The command was expected to run against origin `http://localhost:3500` but the application is at origin `http://www.foobar.com:3500`.\n\nThis commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.')
done()
})
@@ -210,16 +210,16 @@ it('verifies number of cy commands', () => {
// remove custom commands we added for our own testing
const customCommands = ['getAll', 'shouldWithTimeout', 'originLoadUtils']
// @ts-ignore
const actualCommands = Cypress._.reject(Object.keys(cy.commandFns), (command) => customCommands.includes(command))
const actualCommands = Cypress._.pullAll([...Object.keys(cy.commandFns), ...Object.keys(cy.queryFns)], customCommands)
const expectedCommands = [
'check', 'uncheck', 'click', 'dblclick', 'rightclick', 'focus', 'blur', 'hover', 'scrollIntoView', 'scrollTo', 'select',
'selectFile', 'submit', 'type', 'clear', 'trigger', 'as', 'ng', 'should', 'and', 'clock', 'tick', 'spread', 'each', 'then',
'selectFile', 'submit', 'type', 'clear', 'trigger', 'should', 'and', 'clock', 'tick', 'spread', 'each', 'then',
'invoke', 'its', 'getCookie', 'getCookies', 'setCookie', 'clearCookie', 'clearCookies', 'pause', 'debug', 'exec', 'readFile',
'writeFile', 'fixture', 'clearLocalStorage', 'url', 'hash', 'location', 'end', 'noop', 'log', 'wrap', 'reload', 'go', 'visit',
'focused', 'get', 'contains', 'root', 'shadow', 'within', 'request', 'session', 'screenshot', 'task', 'find', 'filter', 'not',
'focused', 'get', 'contains', 'shadow', 'within', 'request', 'session', 'screenshot', 'task', 'find', 'filter', 'not',
'children', 'eq', 'closest', 'first', 'last', 'next', 'nextAll', 'nextUntil', 'parent', 'parents', 'parentsUntil', 'prev',
'prevAll', 'prevUntil', 'siblings', 'wait', 'title', 'window', 'document', 'viewport', 'server', 'route', 'intercept', 'origin',
'mount', 'getAllLocalStorage', 'clearAllLocalStorage', 'getAllSessionStorage', 'clearAllSessionStorage',
'mount', 'as', 'root', 'getAllLocalStorage', 'clearAllLocalStorage', 'getAllSessionStorage', 'clearAllSessionStorage',
]
const addedCommands = Cypress._.difference(actualCommands, expectedCommands)
const removedCommands = Cypress._.difference(expectedCommands, actualCommands)
@@ -1,4 +1,3 @@
const { stripIndent } = require('common-tags')
import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin navigation', { browser: '!webkit' }, () => {
@@ -195,35 +194,6 @@ context('cy.origin navigation', { browser: '!webkit' }, () => {
})
})
// @ts-ignore
it('informs user to use cy.origin with experimental flag off', { experimentalSessionAndOrigin: false }, (done) => {
cy.on('fail', (e) => {
expect(e.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
In order to visit a different origin, you can enable the \`experimentalSessionAndOrigin\` flag and use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/primary-origin.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://www.foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/dom.html')\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
The new URL is considered a different origin because the following parts of the URL are different:\n
> superdomain\n
You may only \`cy.visit()\` same-origin URLs within a single test.\n
The previous URL you visited was:\n
> 'http://localhost:3500'\n
You're attempting to visit this URL:\n
> 'http://www.foobar.com:3500'`)
done()
})
cy.visit('/fixtures/primary-origin.html')
// this call should error since we can't visit a cross-origin
cy.visit('http://www.foobar.com:3500/fixtures/dom.html')
})
it('supports the query string option', () => {
cy.visit('/fixtures/primary-origin.html')
@@ -38,6 +38,8 @@ context('cy.origin querying', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -52,6 +54,8 @@ context('cy.origin querying', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -66,6 +70,8 @@ context('cy.origin querying', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -82,6 +88,8 @@ context('cy.origin querying', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -96,6 +104,8 @@ context('cy.origin querying', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -110,6 +120,8 @@ context('cy.origin querying', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -146,7 +158,7 @@ context('cy.origin querying', { browser: '!webkit' }, () => {
const { consoleProps } = findCrossOriginLogs('contains', logs, 'foobar.com')
expect(consoleProps.Command).to.equal('contains')
expect(consoleProps['Applied To']).to.be.undefined
expect(consoleProps['Applied To']).to.have.property('tagName').that.equals('BODY')
expect(consoleProps.Elements).to.equal(1)
expect(consoleProps.Content).to.equal('Nested Find')
expect(consoleProps.Yielded).to.have.property('tagName').that.equals('DIV')
@@ -4,30 +4,6 @@ context('cy.origin unsupported commands', { browser: '!webkit' }, () => {
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
it('cy.route() method is deprecated', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.route()` has been deprecated and its use is not supported in the `cy.origin()` callback. Consider using `cy.intercept()` (outside of the callback) instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/intercept')
done()
})
cy.origin('http://www.foobar.com:3500', () => {
cy.route('api')
})
})
it('cy.server() method is deprecated', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.server()` has been deprecated and its use is not supported in the `cy.origin()` callback. Consider using `cy.intercept()` (outside of the callback) instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/intercept')
done()
})
cy.origin('http://www.foobar.com:3500', () => {
cy.server()
})
})
it('cy.origin() is not yet supported', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.origin()` use is not currently supported in the `cy.origin()` callback, but is planned for a future release. Please 👍 the following issue and leave a comment with your use-case:')
@@ -1,6 +1,6 @@
import { makeRequestForCookieBehaviorTests as makeRequest } from '../../../support/utils'
describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!webkit' }, () => {
describe('Cookie Behavior', { browser: '!webkit' }, () => {
const serverConfig = {
http: {
sameOriginPort: 3500,
@@ -249,7 +249,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// firefox actually sets the cookie correctly
cy.getCookie('foo1').its('value').should('equal', 'bar1')
} else {
cy.getCookie('foo1').its('value').should('equal', null)
cy.getCookie('foo1').should('equal', null)
}
// FIXME: Ideally, browser should have access to this cookie. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
@@ -296,7 +296,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// firefox actually sets the cookie correctly
cy.getCookie('foo1').its('value').should('equal', 'bar1')
} else {
cy.getCookie('foo1').its('value').should('equal', null)
cy.getCookie('foo1').should('equal', null)
}
// FIXME: Ideally, browser should have access to this cookie. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
@@ -374,7 +374,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// firefox actually sets the cookie correctly
cy.getCookie('foo1').its('value').should('equal', 'bar1')
} else {
cy.getCookie('foo1').its('value').should('equal', null)
cy.getCookie('foo1').should('equal', null)
}
// FIXME: Ideally, browser should have access to this cookie. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
@@ -416,7 +416,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// firefox actually sets the cookie correctly
cy.getCookie('foo1').its('value').should('equal', 'bar1')
} else {
cy.getCookie('foo1').its('value').should('equal', null)
cy.getCookie('foo1').should('equal', null)
}
// FIXME: Ideally, browser should have access to this cookie. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
@@ -525,13 +525,13 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// assert cookie value is actually set in the browser
if (scheme === 'https') {
// FIXME: cy.getCookie does not believe this cookie exists. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
// cy.getCookie('bar1').its('value').should('equal', 'baz1')
} else {
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
}
cy.window().then((win) => {
@@ -566,7 +566,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
})
// FIXME: cy.getCookie does not believe this cookie exists. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
@@ -608,7 +608,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
return cy.wrap(makeRequest(win, `${scheme}://www.barbaz.com:${sameOriginPort}/set-cookie?cookie=bar1=baz1; Domain=barbaz.com`, 'fetch', credentialOption as 'same-origin' | 'omit'))
})
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
cy.window().then((win) => {
return cy.wrap(makeRequest(win, `${scheme}://www.barbaz.com:${sameOriginPort}/test-request`, 'fetch', credentialOption as 'same-origin' | 'omit'))
@@ -645,13 +645,13 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// assert cookie value is actually set in the browser
if (scheme === 'https') {
// FIXME: cy.getCookie does not believe this cookie exists. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
// cy.getCookie('bar1').its('value').should('equal', 'baz1')
} else {
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
}
cy.window().then((win) => {
@@ -691,7 +691,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// assert cookie value is actually set in the browser
// FIXME: cy.getCookie does not believe this cookie exists, though it is set in the browser. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
@@ -861,7 +861,6 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
})
// without cy.origin means the AUT has the same origin as top
// TODO: In the future, this test should be run with the experimentalSessionAndOrigin=true and experimentalSessionAndOrigin=false
describe('w/o cy.origin', () => {
describe('same site / same origin', () => {
describe('XMLHttpRequest', () => {
@@ -1189,13 +1188,13 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// assert cookie value is actually set in the browser
if (scheme === 'https') {
// FIXME: cy.getCookie does not believe this cookie exists, though it is set in the browser. Should be fixed in https://github.com/cypress-io/cypress/pull/23643.
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
// cy.getCookie('bar1').its('value').should('equal', 'baz1')
} else {
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
}
cy.window().then((win) => {
@@ -1220,7 +1219,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
})
// FIXME: cy.getCookie does not believe this cookie exists, though it is set in the browser. Should be fixed in https://github.com/cypress-io/cypress/pull/23643
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
@@ -1251,7 +1250,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
return cy.wrap(makeRequest(win, `${scheme}://www.barbaz.com:${sameOriginPort}/set-cookie?cookie=bar1=baz1; Domain=barbaz.com`, 'fetch', credentialOption as 'same-origin' | 'omit'))
})
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
cy.window().then((win) => {
return cy.wrap(makeRequest(win, `${scheme}://www.barbaz.com:${sameOriginPort}/test-request`, 'fetch', credentialOption as 'same-origin' | 'omit'))
@@ -1277,13 +1276,13 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// assert cookie value is actually set in the browser
if (scheme === 'https') {
// FIXME: cy.getCookie does not believe this cookie exists, though it is set in the browser. Should be fixed in https://github.com/cypress-io/cypress/pull/23643
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
// cy.getCookie('bar1').its('value').should('equal', 'baz1')
} else {
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
}
cy.window().then((win) => {
@@ -1313,7 +1312,7 @@ describe('Cookie Behavior with experimentalSessionAndOrigin=true', { browser: '!
// assert cookie value is actually set in the browser
// FIXME: cy.getCookie does not believe this cookie exists, though it is set in the browser. Should be fixed in https://github.com/cypress-io/cypress/pull/23643
cy.getCookie('bar1').its('value').should('equal', null)
cy.getCookie('bar1').should('equal', null)
// can only set third-party SameSite=None with Secure attribute, which is only possibly over https
//expected future assertion
@@ -204,30 +204,6 @@ describe('cy.origin Cypress API', { browser: '!webkit' }, () => {
})
context('not supported', () => {
it('throws an error when a user attempts to configure Cypress.Server.defaults() inside of cy.origin', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`Cypress.Server.*` has been deprecated and its use is not supported in the `cy.origin()` callback. Consider using `cy.intercept()` (outside of the callback) instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/intercept')
done()
})
cy.origin('http://www.foobar.com:3500', () => {
Cypress.Server.defaults({})
})
})
it('throws an error when a user attempts to configure Cypress.Cookies.preserveOnce() inside of cy.origin', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`Cypress.Cookies.preserveOnce` use is not supported in the `cy.origin()` callback. Consider using `cy.session()` (outside of the callback) instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/session')
done()
})
cy.origin('http://www.foobar.com:3500', () => {
Cypress.Cookies.preserveOnce('')
})
})
it('throws an error when a user attempts to call Cypress.session.clearAllSavedSessions() inside of cy.origin', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`Cypress.session.*` methods are not supported in the `cy.origin()` callback. Consider using them outside of the callback instead.')
@@ -2,7 +2,6 @@ import CryptoJS from 'crypto-js'
import type { TemplateExecutor } from 'lodash'
// NOTE: in order to run these tests, the following config flags need to be set
// experimentalSessionAndOrigin=true
// experimentalModifyObstructiveThirdPartyCode=true
describe('Integrity Preservation', { browser: '!webkit' }, () => {
// Add common SRI hashes used when setting script/link integrity.
@@ -30,8 +30,7 @@ describe('cy.origin logging', { browser: '!webkit' }, () => {
})
})
// TODO: fix flaky test https://github.com/cypress-io/cypress/issues/21300
it.skip('logs cy.origin as group when failing with validation failure', () => {
it('logs cy.origin as group when failing with validation failure', () => {
const logs: any[] = []
cy.on('log:added', (attrs) => {
@@ -51,8 +50,7 @@ describe('cy.origin logging', { browser: '!webkit' }, () => {
cy.origin(false, () => {})
})
// TODO: fix flaky test https://github.com/cypress-io/cypress/issues/21300
it.skip('logs cy.origin as group when failing with serialization failure', () => {
it('logs cy.origin as group when failing with serialization failure', () => {
const logs: any[] = []
cy.on('log:added', (attrs) => {
@@ -251,6 +251,8 @@ describe('errors', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -267,6 +269,8 @@ describe('errors', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://www.idp.com:3500\` but the application is at origin \`http://localhost:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://localhost:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://localhost:3500', () => {\`\n\` <commands targeting http://localhost:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -290,6 +294,8 @@ describe('errors', { browser: '!webkit' }, () => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://www.idp.com:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`This commonly happens when you have either not navigated to the expected origin or have navigated away unexpectedly.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -313,6 +319,8 @@ describe('errors', { browser: '!webkit' }, () => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.idp.com:3500\`.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.idp.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.idp.com:3500', () => {\`\n\` <commands targeting http://www.idp.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -335,6 +343,8 @@ describe('errors', { browser: '!webkit' }, () => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -360,6 +370,8 @@ describe('errors', { browser: '!webkit' }, () => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://www.idp.com:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -388,6 +400,8 @@ describe('errors', { browser: '!webkit' }, () => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://www.idp.com:3500\` but the application is at origin \`http://localhost:3500\`.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://localhost:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://localhost:3500', () => {\`\n\` <commands targeting http://localhost:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -432,6 +446,8 @@ describe('errors', { browser: '!webkit' }, () => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)
@@ -479,6 +495,8 @@ describe('errors', { browser: '!webkit' }, () => {
cy.on('fail', (err) => {
expect(err.message).to.include(`Timed out retrying after 50ms:`)
expect(err.message).to.include(`The command was expected to run against origin \`http://localhost:3500\` but the application is at origin \`http://www.foobar.com:3500\`.`)
expect(err.message).to.include(`Using \`cy.origin()\` to wrap the commands run on \`http://www.foobar.com:3500\` will likely fix this issue.`)
expect(err.message).to.include(`cy.origin('http://www.foobar.com:3500', () => {\`\n\` <commands targeting http://www.foobar.com:3500 go here>\`\n\`})`)
// make sure that the secondary origin failures do NOT show up as spec failures or AUT failures
expect(err.message).not.to.include(`The following error originated from your test code, not from Cypress`)
expect(err.message).not.to.include(`The following error originated from your application code, not from Cypress`)

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