mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-24 07:59:12 -05:00
Merge branch 'develop' of github.com:cypress-io/cypress into chore/merge_dev_15
This commit is contained in:
@@ -12,6 +12,10 @@ _Released 07/01/2025 (PENDING)_
|
||||
|
||||
_Released 4/22/2025 (PENDING)_
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
- The [`cy.press()`](http://on.cypress.io/api/press) command no longer errors when used in specs subsequent to the first spec in run mode. Fixes [#31466](https://github.com/cypress-io/cypress/issues/31466).
|
||||
|
||||
**Misc:**
|
||||
|
||||
- The UI of the reporter and URL were updated to a darker gray background for better color contrast. Addressed in [#31475](https://github.com/cypress-io/cypress/pull/31475).
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
"esModuleInterop": true
|
||||
},
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
"esModuleInterop": true,
|
||||
/** Allows us to strip internal types sourced from webpack */
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
"esModuleInterop": true,
|
||||
/** Allows us to strip internal types sourced from webpack */
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
"esModuleInterop": true
|
||||
},
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
|
||||
@@ -19,98 +19,117 @@ import { ErrorActions } from './actions/ErrorActions'
|
||||
import { EventCollectorActions } from './actions/EventCollectorActions'
|
||||
import { NotificationActions } from './actions/NotificationActions'
|
||||
import { VersionsActions } from './actions/VersionsActions'
|
||||
import { cached } from './util'
|
||||
|
||||
export class DataActions {
|
||||
constructor (private ctx: DataContext) {}
|
||||
private _error: ErrorActions
|
||||
private _file: FileActions
|
||||
private _dev: DevActions
|
||||
private _app: AppActions
|
||||
private _auth: AuthActions
|
||||
private _localSettings: LocalSettingsActions
|
||||
private _wizard: WizardActions
|
||||
private _project: ProjectActions
|
||||
private _electron: ElectronActions
|
||||
private _migration: MigrationActions
|
||||
private _browser: BrowserActions
|
||||
private _servers: ServersActions
|
||||
private _versions: VersionsActions
|
||||
private _eventCollector: EventCollectorActions
|
||||
private _cohorts: CohortsActions
|
||||
private _codegen: CodegenActions
|
||||
private _notification: NotificationActions
|
||||
private _cloudProject: CloudProjectActions
|
||||
|
||||
constructor (private ctx: DataContext) {
|
||||
this._error = new ErrorActions(this.ctx)
|
||||
this._file = new FileActions(this.ctx)
|
||||
this._dev = new DevActions(this.ctx)
|
||||
this._app = new AppActions(this.ctx)
|
||||
this._auth = new AuthActions(this.ctx)
|
||||
this._localSettings = new LocalSettingsActions(this.ctx)
|
||||
this._wizard = new WizardActions(this.ctx)
|
||||
this._project = new ProjectActions(this.ctx)
|
||||
this._electron = new ElectronActions(this.ctx)
|
||||
this._migration = new MigrationActions(this.ctx)
|
||||
this._browser = new BrowserActions(this.ctx)
|
||||
this._servers = new ServersActions(this.ctx)
|
||||
this._versions = new VersionsActions(this.ctx)
|
||||
this._eventCollector = new EventCollectorActions(this.ctx)
|
||||
this._cohorts = new CohortsActions(this.ctx)
|
||||
this._codegen = new CodegenActions(this.ctx)
|
||||
this._notification = new NotificationActions(this.ctx)
|
||||
this._cloudProject = new CloudProjectActions(this.ctx)
|
||||
}
|
||||
|
||||
@cached
|
||||
get error () {
|
||||
return new ErrorActions(this.ctx)
|
||||
return this._error
|
||||
}
|
||||
|
||||
@cached
|
||||
get file () {
|
||||
return new FileActions(this.ctx)
|
||||
return this._file
|
||||
}
|
||||
|
||||
@cached
|
||||
get dev () {
|
||||
return new DevActions(this.ctx)
|
||||
return this._dev
|
||||
}
|
||||
|
||||
@cached
|
||||
get app () {
|
||||
return new AppActions(this.ctx)
|
||||
return this._app
|
||||
}
|
||||
|
||||
@cached
|
||||
get auth () {
|
||||
return new AuthActions(this.ctx)
|
||||
return this._auth
|
||||
}
|
||||
|
||||
@cached
|
||||
get localSettings () {
|
||||
return new LocalSettingsActions(this.ctx)
|
||||
return this._localSettings
|
||||
}
|
||||
|
||||
@cached
|
||||
get wizard () {
|
||||
return new WizardActions(this.ctx)
|
||||
return this._wizard
|
||||
}
|
||||
|
||||
@cached
|
||||
get project () {
|
||||
return new ProjectActions(this.ctx)
|
||||
return this._project
|
||||
}
|
||||
|
||||
@cached
|
||||
get electron () {
|
||||
return new ElectronActions(this.ctx)
|
||||
return this._electron
|
||||
}
|
||||
|
||||
@cached
|
||||
get migration () {
|
||||
return new MigrationActions(this.ctx)
|
||||
return this._migration
|
||||
}
|
||||
|
||||
@cached
|
||||
get browser () {
|
||||
return new BrowserActions(this.ctx)
|
||||
return this._browser
|
||||
}
|
||||
|
||||
@cached
|
||||
get servers () {
|
||||
return new ServersActions(this.ctx)
|
||||
return this._servers
|
||||
}
|
||||
|
||||
@cached
|
||||
get versions () {
|
||||
return new VersionsActions(this.ctx)
|
||||
return this._versions
|
||||
}
|
||||
|
||||
@cached
|
||||
get eventCollector () {
|
||||
return new EventCollectorActions(this.ctx)
|
||||
return this._eventCollector
|
||||
}
|
||||
|
||||
@cached
|
||||
get cohorts () {
|
||||
return new CohortsActions(this.ctx)
|
||||
return this._cohorts
|
||||
}
|
||||
|
||||
@cached
|
||||
get codegen () {
|
||||
return new CodegenActions(this.ctx)
|
||||
return this._codegen
|
||||
}
|
||||
|
||||
@cached
|
||||
get notification () {
|
||||
return new NotificationActions(this.ctx)
|
||||
return this._notification
|
||||
}
|
||||
|
||||
@cached
|
||||
get cloudProject () {
|
||||
return new CloudProjectActions(this.ctx)
|
||||
return this._cloudProject
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
GraphQLDataSource,
|
||||
RemoteRequestDataSource,
|
||||
} from './sources'
|
||||
import { cached } from './util/cached'
|
||||
import type { GraphQLSchema, OperationTypeNode, DocumentNode } from 'graphql'
|
||||
import type { IncomingHttpHeaders } from 'http'
|
||||
// tslint:disable-next-line no-implicit-dependencies - electron dep needs to be defined
|
||||
@@ -86,6 +85,24 @@ export class DataContext {
|
||||
private _config: Omit<DataContextConfig, 'modeOptions'>
|
||||
private _modeOptions: Partial<AllModeOptions>
|
||||
private _coreData: CoreDataShape
|
||||
private _graphql: GraphQLDataSource
|
||||
private _remoteRequest: RemoteRequestDataSource
|
||||
private _file: FileDataSource
|
||||
private _versions: VersionsDataSource
|
||||
private _browser: BrowserDataSource
|
||||
private _actions: DataActions
|
||||
private _wizard: WizardDataSource
|
||||
private _project: ProjectDataSource
|
||||
private _relevantRuns: RelevantRunsDataSource
|
||||
private _relevantRunSpecs: RelevantRunSpecsDataSource
|
||||
private _cloud: CloudDataSource
|
||||
private _env: EnvDataSource
|
||||
private _emitter: DataEmitterActions
|
||||
private _html: HtmlDataSource
|
||||
private _error: ErrorDataSource
|
||||
private _util: UtilDataSource
|
||||
private _migration: MigrationDataSource
|
||||
|
||||
readonly lifecycleManager: ProjectLifecycleManager
|
||||
|
||||
constructor (_config: DataContextConfig) {
|
||||
@@ -94,6 +111,33 @@ export class DataContext {
|
||||
this._config = rest
|
||||
this._modeOptions = modeOptions ?? {} // {} For legacy tests
|
||||
this._coreData = _config.coreData ?? makeCoreData(this._modeOptions)
|
||||
this._graphql = new GraphQLDataSource()
|
||||
this._remoteRequest = new RemoteRequestDataSource()
|
||||
this._file = new FileDataSource(this)
|
||||
this._versions = new VersionsDataSource(this)
|
||||
this._browser = new BrowserDataSource(this)
|
||||
this._actions = new DataActions(this)
|
||||
this._wizard = new WizardDataSource(this)
|
||||
this._project = new ProjectDataSource(this)
|
||||
this._relevantRuns = new RelevantRunsDataSource(this)
|
||||
this._relevantRunSpecs = new RelevantRunSpecsDataSource(this)
|
||||
this._cloud = new CloudDataSource({
|
||||
fetch: (input: RequestInfo | URL, init?: RequestInit) => this.util.fetch(input, init),
|
||||
getUser: () => this.coreData.user,
|
||||
logout: () => this.actions.auth.logout().catch(this.logTraceError),
|
||||
invalidateClientUrqlCache: () => this.graphql.invalidateClientUrqlCache(this),
|
||||
headers: {
|
||||
getMachineId: this.coreData.machineId,
|
||||
},
|
||||
})
|
||||
|
||||
this._env = new EnvDataSource(this)
|
||||
this._emitter = new DataEmitterActions(this)
|
||||
this._html = new HtmlDataSource(this)
|
||||
this._error = new ErrorDataSource(this)
|
||||
this._util = new UtilDataSource(this)
|
||||
this._migration = new MigrationDataSource(this)
|
||||
// the lifecycle manager needs to be initialized last as it needs properties instantiated on the DataContext object
|
||||
this.lifecycleManager = new ProjectLifecycleManager(this)
|
||||
}
|
||||
|
||||
@@ -113,14 +157,12 @@ export class DataContext {
|
||||
return !this.isRunMode
|
||||
}
|
||||
|
||||
@cached
|
||||
get graphql () {
|
||||
return new GraphQLDataSource()
|
||||
return this._graphql
|
||||
}
|
||||
|
||||
@cached
|
||||
get remoteRequest () {
|
||||
return new RemoteRequestDataSource()
|
||||
return this._remoteRequest
|
||||
}
|
||||
|
||||
get modeOptions (): Readonly<Partial<AllModeOptions>> {
|
||||
@@ -131,95 +173,71 @@ export class DataContext {
|
||||
return this._coreData
|
||||
}
|
||||
|
||||
@cached
|
||||
get file () {
|
||||
return new FileDataSource(this)
|
||||
return this._file
|
||||
}
|
||||
|
||||
@cached
|
||||
get versions () {
|
||||
return new VersionsDataSource(this)
|
||||
return this._versions
|
||||
}
|
||||
|
||||
@cached
|
||||
get browser () {
|
||||
return new BrowserDataSource(this)
|
||||
return this._browser
|
||||
}
|
||||
|
||||
/**
|
||||
* All mutations (update / delete / create), fs writes, etc.
|
||||
* should run through this namespace. Everything else should be a "getter"
|
||||
*/
|
||||
@cached
|
||||
get actions () {
|
||||
return new DataActions(this)
|
||||
return this._actions
|
||||
}
|
||||
|
||||
@cached
|
||||
get wizard () {
|
||||
return new WizardDataSource(this)
|
||||
return this._wizard
|
||||
}
|
||||
|
||||
get currentProject () {
|
||||
return this.coreData.currentProject
|
||||
}
|
||||
|
||||
@cached
|
||||
get project () {
|
||||
return new ProjectDataSource(this)
|
||||
return this._project
|
||||
}
|
||||
|
||||
@cached
|
||||
get relevantRuns () {
|
||||
return new RelevantRunsDataSource(this)
|
||||
return this._relevantRuns
|
||||
}
|
||||
|
||||
@cached
|
||||
get relevantRunSpecs () {
|
||||
return new RelevantRunSpecsDataSource(this)
|
||||
return this._relevantRunSpecs
|
||||
}
|
||||
|
||||
@cached
|
||||
get cloud () {
|
||||
return new CloudDataSource({
|
||||
fetch: (input: RequestInfo | URL, init?: RequestInit) => this.util.fetch(input, init),
|
||||
getUser: () => this.coreData.user,
|
||||
logout: () => this.actions.auth.logout().catch(this.logTraceError),
|
||||
invalidateClientUrqlCache: () => this.graphql.invalidateClientUrqlCache(this),
|
||||
headers: {
|
||||
getMachineId: this.coreData.machineId,
|
||||
},
|
||||
})
|
||||
return this._cloud
|
||||
}
|
||||
|
||||
@cached
|
||||
get env () {
|
||||
return new EnvDataSource(this)
|
||||
return this._env
|
||||
}
|
||||
|
||||
@cached
|
||||
get emitter () {
|
||||
return new DataEmitterActions(this)
|
||||
return this._emitter
|
||||
}
|
||||
|
||||
@cached
|
||||
get html () {
|
||||
return new HtmlDataSource(this)
|
||||
return this._html
|
||||
}
|
||||
|
||||
@cached
|
||||
get error () {
|
||||
return new ErrorDataSource(this)
|
||||
return this._error
|
||||
}
|
||||
|
||||
@cached
|
||||
get util () {
|
||||
return new UtilDataSource(this)
|
||||
return this._util
|
||||
}
|
||||
|
||||
@cached
|
||||
get migration () {
|
||||
return new MigrationDataSource(this)
|
||||
return this._migration
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,12 +249,10 @@ export class DataContext {
|
||||
|
||||
// Utilities
|
||||
|
||||
@cached
|
||||
get fs () {
|
||||
return fsExtra
|
||||
}
|
||||
|
||||
@cached
|
||||
get path () {
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* A cached decorator means the value is lazily evaluated once and
|
||||
* the result is cached on the class instance.
|
||||
*/
|
||||
export const cached = <T>(
|
||||
target: T,
|
||||
key: PropertyKey,
|
||||
descriptor: PropertyDescriptor,
|
||||
): void => {
|
||||
if (!descriptor) {
|
||||
descriptor = Object.getOwnPropertyDescriptor(
|
||||
target,
|
||||
key,
|
||||
) as PropertyDescriptor
|
||||
}
|
||||
|
||||
const originalMethod = descriptor.get
|
||||
const isStatic = Object.getPrototypeOf(target) === Function.prototype
|
||||
|
||||
if (isStatic) {
|
||||
throw new Error(`Don't use @cached decorator on static properties`)
|
||||
}
|
||||
|
||||
if (!originalMethod) {
|
||||
throw new Error('@cached can only decorate getters!')
|
||||
} else if (!descriptor.configurable) {
|
||||
throw new Error('@cached target must be configurable')
|
||||
} else {
|
||||
descriptor.get = function () {
|
||||
// eslint-disable-next-line
|
||||
const value = originalMethod.apply(this, arguments as any)
|
||||
const newDescriptor: PropertyDescriptor = {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
value,
|
||||
}
|
||||
|
||||
Object.defineProperty(this, key, newDescriptor)
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
export * from './DocumentNodeBuilder'
|
||||
export * from './autoBindDebug'
|
||||
export * from './cached'
|
||||
export * from './config-file-updater'
|
||||
export * from './file'
|
||||
export * from './hasTypescript'
|
||||
|
||||
@@ -104,6 +104,14 @@ export const BIDI_VALUE: KeyCodeLookup = {
|
||||
'Tab': '\uE004',
|
||||
}
|
||||
|
||||
async function getActiveWindow (client: Client) {
|
||||
try {
|
||||
return await client.getWindowHandle()
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function bidiKeyPress ({ key }: KeyPressParams, client: Client, autContext: string, idSuffix?: string): Promise<void> {
|
||||
const value = BIDI_VALUE[key]
|
||||
|
||||
@@ -111,17 +119,41 @@ export async function bidiKeyPress ({ key }: KeyPressParams, client: Client, aut
|
||||
throw new InvalidKeyError(key)
|
||||
}
|
||||
|
||||
const autFrameElement = await client.findElement('css selector', 'iframe.aut-iframe')
|
||||
const activeElement = await client.getActiveElement()
|
||||
const activeWindow = await getActiveWindow(client)
|
||||
const { contexts: [{ context: topLevelContext }] } = await client.browsingContextGetTree({})
|
||||
|
||||
if (!isEqual(autFrameElement, activeElement)) {
|
||||
await client.scriptEvaluate(
|
||||
{
|
||||
expression: `window.focus()`,
|
||||
target: { context: autContext },
|
||||
awaitPromise: false,
|
||||
},
|
||||
)
|
||||
// TODO: refactor for Cy15 https://github.com/cypress-io/cypress/issues/31480
|
||||
if (activeWindow !== topLevelContext) {
|
||||
debug('Primary window is not currently active; attempting to activate')
|
||||
try {
|
||||
await client.switchToWindow(topLevelContext)
|
||||
} catch (e) {
|
||||
debug('Error while attempting to activate main browser tab:', e)
|
||||
const err = new Error(`Unable to activate main browser tab: ${e?.message || 'Unknown Error Occurred'}. DEBUG namespace cypress:server:automation:command:keypress for more information.`)
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const autFrameElement = await client.findElement('css selector', 'iframe.aut-iframe')
|
||||
const activeElement = await client.getActiveElement()
|
||||
|
||||
if (!isEqual(autFrameElement, activeElement)) {
|
||||
debug('aut iframe is not currently focused; focusing aut iframe: ', autContext)
|
||||
await client.scriptEvaluate(
|
||||
{
|
||||
expression: `window.focus()`,
|
||||
target: { context: autContext },
|
||||
awaitPromise: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
debug('Error occurred during aut frame focus detection:', e)
|
||||
const err = new Error(`Unable to focus the AUT iframe: ${e?.message || 'Unknown Error Occurred'}. DEBUG namespace cypress:server:automation:command:keypress for more information.`)
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -138,6 +170,8 @@ export async function bidiKeyPress ({ key }: KeyPressParams, client: Client, aut
|
||||
})
|
||||
} catch (e) {
|
||||
debug(e)
|
||||
throw e
|
||||
const err = new Error(`Unable to perform key press command for '${key}' key: ${e?.message || 'Unknown Error Occurred'}. DEBUG namespace cypress:server:automation:command:keypress for more information.`)
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ export class BidiAutomation {
|
||||
public readonly automationMiddleware: AutomationMiddleware = {
|
||||
onRequest: async <T extends keyof AutomationCommands> (message: T, data: AutomationCommands[T]['dataType']): Promise<AutomationCommands[T]['returnType']> => {
|
||||
debugVerbose('automation command \'%s\' requested with data: %O', message, data)
|
||||
|
||||
debug('BiDi middleware handling msg `%s` for top context %s', message, this.topLevelContextId)
|
||||
switch (message) {
|
||||
case 'key:press':
|
||||
if (this.autContextId) {
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import type Sinon from 'sinon'
|
||||
import type { expect as Expect } from 'chai'
|
||||
import type { KeyPressSupportedKeys } from '@packages/types'
|
||||
import type { SendDebuggerCommand } from '../../../../lib/browsers/cdp_automation'
|
||||
import { cdpKeyPress, bidiKeyPress, BIDI_VALUE, CDP_KEYCODE } from '../../../../lib/automation/commands/key_press'
|
||||
import { Client as WebdriverClient } from 'webdriver'
|
||||
import type { Protocol } from 'devtools-protocol'
|
||||
const { expect, sinon } = require('../../../spec_helper')
|
||||
const { expect, sinon }: { expect: typeof Expect, sinon: Sinon.SinonSandbox } = require('../../../spec_helper')
|
||||
|
||||
type ClientParams<T extends keyof WebdriverClient> = WebdriverClient[T] extends (...args: any[]) => any ?
|
||||
Parameters<WebdriverClient[T]> :
|
||||
never
|
||||
|
||||
type ClientReturn<T extends keyof WebdriverClient> = WebdriverClient[T] extends (...args: any[]) => any ?
|
||||
ReturnType<WebdriverClient[T]> :
|
||||
never
|
||||
|
||||
describe('key:press automation command', () => {
|
||||
describe('cdp()', () => {
|
||||
describe('cdp', () => {
|
||||
let sendFn: Sinon.SinonStub<Parameters<SendDebuggerCommand>, ReturnType<SendDebuggerCommand>>
|
||||
const topFrameId = 'abc'
|
||||
const autFrameId = 'def'
|
||||
@@ -178,16 +187,20 @@ describe('key:press automation command', () => {
|
||||
const otherElement = {
|
||||
'element-6066-11e4-a52e-4f735466cecf': 'uuid-2',
|
||||
}
|
||||
const topLevelContext = 'b7173d71-c76c-41ec-beff-25a72f7cae13'
|
||||
|
||||
beforeEach(() => {
|
||||
// can't create a sinon stubbed instance because webdriver doesn't export the constructor. Because it's known that
|
||||
// bidiKeypress only invokes inputPerformActions, and inputPerformActions is properly typed, this is okay.
|
||||
// @ts-expect-error
|
||||
client = {
|
||||
inputPerformActions: (sinon as Sinon.SinonSandbox).stub<Parameters<WebdriverClient['inputPerformActions']>, ReturnType<WebdriverClient['inputPerformActions']>>(),
|
||||
getActiveElement: (sinon as Sinon.SinonSandbox).stub<Parameters<WebdriverClient['getActiveElement']>, ReturnType<WebdriverClient['getActiveElement']>>(),
|
||||
findElement: (sinon as Sinon.SinonSandbox).stub<Parameters<WebdriverClient['findElement']>, ReturnType<WebdriverClient['findElement']>>(),
|
||||
scriptEvaluate: (sinon as Sinon.SinonSandbox).stub<Parameters<WebdriverClient['scriptEvaluate']>, ReturnType<WebdriverClient['scriptEvaluate']>>(),
|
||||
inputPerformActions: sinon.stub<ClientParams<'inputPerformActions'>, ClientReturn<'inputPerformActions'>>(),
|
||||
getActiveElement: sinon.stub<ClientParams<'getActiveElement'>, ClientReturn<'getActiveElement'>>(),
|
||||
findElement: sinon.stub<ClientParams<'findElement'>, ClientReturn<'findElement'>>(),
|
||||
scriptEvaluate: sinon.stub<ClientParams<'scriptEvaluate'>, ClientReturn<'scriptEvaluate'>>(),
|
||||
getWindowHandle: sinon.stub<ClientParams<'getWindowHandle'>, ClientReturn<'getWindowHandle'>>(),
|
||||
switchToWindow: sinon.stub<ClientParams<'switchToWindow'>, ClientReturn<'switchToWindow'>>().resolves(),
|
||||
browsingContextGetTree: sinon.stub<ClientParams<'browsingContextGetTree'>, ClientReturn<'browsingContextGetTree'>>(),
|
||||
}
|
||||
|
||||
autContext = 'someContextId'
|
||||
@@ -195,10 +208,21 @@ describe('key:press automation command', () => {
|
||||
key = 'Tab'
|
||||
|
||||
client.inputPerformActions.resolves()
|
||||
client.browsingContextGetTree.resolves({
|
||||
contexts: [
|
||||
{
|
||||
context: topLevelContext,
|
||||
children: [],
|
||||
url: 'someUrl',
|
||||
userContext: 'userContext',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the aut iframe is not in focus', () => {
|
||||
beforeEach(() => {
|
||||
client.getWindowHandle.resolves(topLevelContext)
|
||||
client.findElement.withArgs('css selector ', 'iframe.aut-iframe').resolves(iframeElement)
|
||||
// @ts-expect-error - webdriver types show this returning a string, but it actually returns an ElementReference, same as findElement
|
||||
client.getActiveElement.resolves(otherElement)
|
||||
@@ -226,7 +250,41 @@ describe('key:press automation command', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when webdriver classic has no active window', () => {
|
||||
beforeEach(() => {
|
||||
client.getWindowHandle.rejects(new Error())
|
||||
})
|
||||
|
||||
it('activates the top level context window', async () => {
|
||||
await bidiKeyPress({ key }, client as WebdriverClient, autContext, 'idSuffix')
|
||||
expect(client.switchToWindow).to.have.been.calledWith(topLevelContext)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when webdriver classic has the top level context as the active window', () => {
|
||||
beforeEach(() => {
|
||||
client.getWindowHandle.resolves(topLevelContext)
|
||||
})
|
||||
|
||||
it('does not activate the top level context window', async () => {
|
||||
await bidiKeyPress({ key }, client as WebdriverClient, autContext, 'idSuffix')
|
||||
expect(client.switchToWindow).not.to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
describe('when webdriver classic has a different window than the top level context as the active window', () => {
|
||||
beforeEach(() => {
|
||||
client.getWindowHandle.resolves('fa54442b-bc42-45fa-9996-88b7fd066211')
|
||||
})
|
||||
|
||||
it('activates the top level context window', async () => {
|
||||
await bidiKeyPress({ key }, client as WebdriverClient, autContext, 'idSuffix')
|
||||
expect(client.switchToWindow).to.have.been.calledWith(topLevelContext)
|
||||
})
|
||||
})
|
||||
|
||||
it('calls client.inputPerformActions with a keydown and keyup action', async () => {
|
||||
client.getWindowHandle.resolves(topLevelContext)
|
||||
client.findElement.withArgs('css selector ', 'iframe.aut-iframe').resolves(iframeElement)
|
||||
// @ts-expect-error - webdriver types show this returning a string, but it actually returns an ElementReference, same as findElement
|
||||
client.getActiveElement.resolves(iframeElement)
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"target": "ES2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"lib": ["es2021"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
"jsx": "react", /* Specify what JSX code is generated. */
|
||||
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
|
||||
@@ -101,7 +101,6 @@ export async function makePackage () {
|
||||
'outDir': 'dist',
|
||||
'noImplicitAny': true,
|
||||
'resolveJsonModule': true,
|
||||
'experimentalDecorators': true,
|
||||
'noUncheckedIndexedAccess': true,
|
||||
'importsNotUsedAsValues': 'error',
|
||||
'types': [],
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
e2e: {
|
||||
supportFile: false,
|
||||
setupNodeEvents (on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
describe('this one should pass', () => {
|
||||
it('dispatches a key press', () => {
|
||||
cy.press(Cypress.Keyboard.Keys.TAB)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
describe('it should also pass', () => {
|
||||
it('dispatches a key press', () => {
|
||||
cy.press(Cypress.Keyboard.Keys.TAB)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import systemTests from '../lib/system-tests'
|
||||
|
||||
describe('e2e issue 31466: cy.press only works in the first spec in firefox', () => {
|
||||
systemTests.setup()
|
||||
|
||||
systemTests.it('does not error when dispatching cy.press', {
|
||||
spec: 'first_spec.cy.js,second_spec.cy.js',
|
||||
project: 'cy-press-second-spec-error',
|
||||
expectedExitCode: 0,
|
||||
browser: 'firefox',
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,9 @@
|
||||
import systemTests from '../lib/system-tests'
|
||||
import childProcess from 'child_process'
|
||||
|
||||
// these system tests are skipped in webkit due to flake.
|
||||
// TODO: https://github.com/cypress-io/cypress/issues/31503
|
||||
|
||||
describe('cy.pause() in run mode', () => {
|
||||
systemTests.setup()
|
||||
|
||||
@@ -15,6 +18,7 @@ describe('cy.pause() in run mode', () => {
|
||||
headed: true,
|
||||
noExit: true,
|
||||
expectedExitCode: null,
|
||||
browser: '!webkit',
|
||||
onSpawn: (cp) => {
|
||||
cp.stdout.on('data', (buf) => {
|
||||
if (buf.toString().includes('not exiting due to options.exit being false')) {
|
||||
@@ -38,6 +42,7 @@ describe('cy.pause() in run mode', () => {
|
||||
headed: false,
|
||||
noExit: true,
|
||||
expectedExitCode: null,
|
||||
browser: '!webkit',
|
||||
onSpawn: (cp) => {
|
||||
cp.stdout.on('data', (buf) => {
|
||||
if (buf.toString().includes('not exiting due to options.exit being false')) {
|
||||
@@ -62,6 +67,7 @@ describe('cy.pause() in run mode', () => {
|
||||
headed: true,
|
||||
noExit: false,
|
||||
expectedExitCode: 0,
|
||||
browser: '!webkit',
|
||||
})
|
||||
|
||||
systemTests.it('does not pause without --headed and --no-exit', {
|
||||
@@ -75,5 +81,6 @@ describe('cy.pause() in run mode', () => {
|
||||
headed: false,
|
||||
noExit: false,
|
||||
expectedExitCode: 0,
|
||||
browser: '!webkit',
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user