Merge branch 'develop' into mschile/v13_merge_develop

This commit is contained in:
Matt Schile
2023-07-14 13:41:53 -06:00
35 changed files with 621 additions and 436 deletions
+14 -32
View File
@@ -30,7 +30,6 @@ mainBuildFilters: &mainBuildFilters
- /^release\/\d+\.\d+\.\d+$/
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'ryanm/feat/change-test-isolation-logic'
# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
@@ -41,7 +40,6 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'ryanm/feat/change-test-isolation-logic', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -52,7 +50,6 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'ryanm/feat/change-test-isolation-logic', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -72,7 +69,6 @@ windowsWorkflowFilters: &windows-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'ryanm/feat/change-test-isolation-logic', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -119,8 +115,11 @@ executors:
environment:
PLATFORM: windows
# see https://circleci.com/docs/configuration-reference/#macos-execution-environment
darwin-arm64: &darwin-arm64-executor
machine: true
macos:
xcode: "14.2.0"
resource_class: macos.m1.large.gen1
environment:
PLATFORM: darwin
@@ -1356,12 +1355,18 @@ jobs:
steps:
- restore_cached_workspace
- restore_cached_system_tests_deps
# TODO: Remove this once we switch off self-hosted M1 runners
- when:
condition:
equal: [ *darwin-arm64-executor, << parameters.executor >> ]
or:
- equal: [ *linux-arm64-executor, << parameters.executor >> ] # TODO: Figure out how to support linux-arm64 when we get to linux arm64 build: https://github.com/cypress-io/cypress/issues/23557
steps:
- run: rm -f /tmp/cypress/junit/*
- run:
name: Run v8 integration tests
command: |
source ./scripts/ensure-node.sh
yarn test-integration --scope "'@tooling/packherd'"
- verify-mocha-results:
expectedResultCount: 1
- unless:
condition:
or:
@@ -1374,18 +1379,6 @@ jobs:
yarn test-integration --scope "'@tooling/{packherd,v8-snapshot,electron-mksnapshot}'"
- verify-mocha-results:
expectedResultCount: 3
- when:
condition:
or:
- equal: [ *linux-arm64-executor, << parameters.executor >> ]
steps:
- run:
name: Run v8 integration tests
command: |
source ./scripts/ensure-node.sh
yarn test-integration --scope "'@tooling/packherd'"
- verify-mocha-results:
expectedResultCount: 1
- store_test_results:
path: /tmp/cypress
- store-npm-logs
@@ -1521,12 +1514,6 @@ jobs:
parallelism: 1
steps:
- restore_cached_workspace
# TODO: Remove this once we switch off self-hosted M1 runners
- when:
condition:
equal: [ *darwin-arm64-executor, << parameters.executor >> ]
steps:
- run: rm -f /tmp/cypress/junit/*
- run: yarn workspace @packages/server test-unit cloud/environment_spec.ts
- verify-mocha-results:
expectedResultCount: 1
@@ -2865,13 +2852,11 @@ darwin-arm64-workflow: &darwin-arm64-workflow
- node_modules_install:
name: darwin-arm64-node-modules-install
executor: darwin-arm64
resource_class: cypress-io/latest_m1
only-cache-for-root-user: true
- build:
name: darwin-arm64-build
executor: darwin-arm64
resource_class: cypress-io/latest_m1
requires:
- darwin-arm64-node-modules-install
@@ -2883,26 +2868,23 @@ darwin-arm64-workflow: &darwin-arm64-workflow
- test-runner:commit-status-checks
- test-runner:build-binary
executor: darwin-arm64
resource_class: cypress-io/latest_m1
resource_class: large
requires:
- darwin-arm64-build
- v8-integration-tests:
name: darwin-arm64-v8-integration-tests
executor: darwin-arm64
resource_class: cypress-io/latest_m1
requires:
- darwin-arm64-build
- driver-integration-memory-tests:
name: darwin-arm64-driver-integration-memory-tests
executor: darwin-arm64
resource_class: cypress-io/latest_m1
requires:
- darwin-arm64-build
- server-unit-tests-cloud-environment:
name: darwin-arm64-server-unit-tests-cloud-environment
executor: darwin-arm64
resource_class: cypress-io/latest_m1
requires:
- darwin-arm64-build
+31 -3
View File
@@ -41,6 +41,7 @@ jobs:
with:
node-version: 'lts/*'
- name: Generate Non-mono Repo Open / Closed KPIs
id: non-mono-repo-open-closed-metrics
uses: actions/github-script@v6
env:
DEFAULT_REPOS: ${{ github.event.inputs.default-other-repos || '["cypress-documentation", "circleci-orb", "github-action", "cypress-docker-images", "cypress-chrome-recorder-extension", "cypress-chrome-recorder", "cypress-recorder-extension", "cypress-example-kitchensink", "cypress-origin-providers", "release-automations", "cypress-test-tiny", "eslint-plugin-cypress", "netlify-plugin-cypress", "cypress-support-internal", "cypress-realworld-app", "code-coverage"]' }}
@@ -48,8 +49,9 @@ jobs:
github-token: ${{ secrets.TRIAGE_BOARD_TOKEN }}
script: |
const script = require('./scripts/reports/open-vs-closed-issues.js')
await script.getOpenAndClosedIssueMetrics(github, context, ${{ env.DEFAULT_REPOS }});
await script.getOpenAndClosedIssueMetrics(github, context, core, ${{ env.DEFAULT_REPOS }});
- name: Generate Cypress Mono Repo Open / Closed KPIs
id: mono-repo-open-closed-metrics
uses: actions/github-script@v6
env:
DEFAULT_MONO_REPO: ${{ github.event.inputs.default-mono-repo || '["cypress"]' }}
@@ -57,8 +59,9 @@ jobs:
github-token: ${{ secrets.TRIAGE_BOARD_TOKEN }}
script: |
const script = require('./scripts/reports/open-vs-closed-issues.js')
await script.getOpenAndClosedIssueMetrics(github, context, ${{ env.DEFAULT_MONO_REPO }});
await script.getOpenAndClosedIssueMetrics(github, context, core, ${{ env.DEFAULT_MONO_REPO }});
- name: Generate Triage Throughput KPIs
id: triage-metrics
uses: actions/github-script@v6
env:
START_DATE: ${{ github.event.inputs.start-date }}
@@ -68,4 +71,29 @@ jobs:
github-token: ${{ secrets.TRIAGE_BOARD_TOKEN }}
script: |
const script = require('./scripts/reports/triage_throughput_kpis.js')
await script.getTriageIssueMetrics(github, context, "${{ env.START_DATE }}", "${{ env.END_DATE }}", "${{ env.PROJECT_BOARD_NUMBER }}");
await script.getTriageIssueMetrics(github, context, core, "${{ env.START_DATE }}", "${{ env.END_DATE }}", "${{ env.PROJECT_BOARD_NUMBER }}");
- name: Generate Mitigation KPIs
id: mitigation-metrics
uses: actions/github-script@v6
env:
START_DATE: ${{ github.event.inputs.start-date }}
END_DATE: ${{ github.event.inputs.end-date }}
PROJECT_BOARD_NUMBER: 9
with:
github-token: ${{ secrets.TRIAGE_BOARD_TOKEN }}
script: |
const script = require('./scripts/reports/triage_mitigation_kpis.js')
await script.getIssueMitigationMetrics(github, context, core, "${{ env.START_DATE }}", "${{ env.END_DATE }}", "${{ env.PROJECT_BOARD_NUMBER }}");
- name: Generate KPI Report
id: generate-report
uses: actions/github-script@v6
env:
START_DATE: ${{ github.event.inputs.start-date }}
END_DATE: ${{ github.event.inputs.end-date }}
PROJECT_BOARD_NUMBER: 9
with:
github-token: ${{ secrets.TRIAGE_BOARD_TOKEN }}
script: |
const script = require('./scripts/reports/generate_kpi_report.js')
await script.generateKPIReport(github, context, core, ${{ steps.non-mono-repo-open-closed-metrics.outputs.results }}, ${{ steps.mono-repo-open-closed-metrics.outputs.results }}, ${{ steps.triage-metrics.outputs.results }}, ${{ steps.mitigation-metrics.outputs.results }} );
+26 -3
View File
@@ -12,25 +12,48 @@ _Released 08/1/2023 (PENDING)_
- The deprecated configuration option, `nodeVersion` has been removed. Addresses [#27016](https://github.com/cypress-io/cypress/issues/27016).
## 12.17.2
_Released 07/18/2023 (PENDING)_
**Bugfixes:**
- Fixed an issue where `cy.writeFile()` would erroneously fail with the error `cy.writeFile() must only be invoked from the spec file or support file`. Fixes [#27097](https://github.com/cypress-io/cypress/issues/27097).
## 12.17.1
_Released 07/10/2023_
**Bugfixes:**
- Fixed invalid stored preference when enabling in-app notifications that could cause the application to crash. Fixes [#27228](https://github.com/cypress-io/cypress/issues/27228).
- Fixed an issue with the Typescript types of [`cy.screenshot()`](https://docs.cypress.io/api/commands/screenshot). Fixed in [#27130](https://github.com/cypress-io/cypress/pull/27130).
**Dependency Updates:**
- Upgraded [`@cypress/request`](https://www.npmjs.com/package/@cypress/request) from `2.88.10` to `2.88.11` to address [CVE-2022-24999](https://www.cve.org/CVERecord?id=CVE-2022-24999) security vulnerability. Addressed in [#27005](https://github.com/cypress-io/cypress/pull/27005).
## 12.17.0
_Released 07/05/2023 (PENDING)_
_Released 07/05/2023_
**Features:**
- Cypress Cloud users can now receive desktop notifications about their runs, including when one starts, finishes, or fails. Addessed in [#27078](https://github.com/cypress-io/cypress/pull/27078). Addresses [#26686](https://github.com/cypress-io/cypress/issues/26686).
- Cypress Cloud users can now receive desktop notifications about their runs, including when one starts, finishes, or fails. Addresses [#26686](https://github.com/cypress-io/cypress/issues/26686).
**Bugfixes:**
- Fixed issues where commands would fail with the error `must only be invoked from the spec file or support file`. Fixes [#27149](https://github.com/cypress-io/cypress/issues/27149) and [#27163](https://github.com/cypress-io/cypress/issues/27163).
- Fixed a regression introduced in Cypress [12.12.0](#12-12-0) where Cypress may fail to reconnect to the Chrome DevTools Protocol in Electron. Fixes [#26900](https://github.com/cypress-io/cypress/issues/26900).
- Fixed an issue where chrome was not recovering from browser crashes properly. Fixes [#24650](https://github.com/cypress-io/cypress/issues/24650).
- Fixed a race condition that was causing a GraphQL error to appear on the [Debug page](https://docs.cypress.io/guides/cloud/runs#Debug) when viewing a running Cypress Cloud build. Fixed in [#27134](https://github.com/cypress-io/cypress/pull/27134).
- Fixed a race condition in electron where the test window exiting prematurely during the browser launch process was causing the whole test run to fail. Addressed in [#27167](https://github.com/cypress-io/cypress/pull/27167).
- Fixed minor issues with Typescript types in the CLI. Fixes [#24110](https://github.com/cypress-io/cypress/issues/24110).
- Fixed an issue where a value for the Electron debug port would not be respected if defined using the `ELECTRON_EXTRA_LAUNCH_ARGS` environment variable. Fixes [#26711](https://github.com/cypress-io/cypress/issues/26711).
**Dependency Updates:**
- Update dependency semver to ^7.5.3. Addressed in [#27151](https://github.com/cypress-io/cypress/pull/27151).
- Update dependency semver to ^7.5.3. Addressed in [#27151](https://github.com/cypress-io/cypress/pull/27151).
## 12.16.0
+1 -1
View File
@@ -20,7 +20,7 @@
"unit": "cross-env BLUEBIRD_DEBUG=1 NODE_ENV=test mocha --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json"
},
"dependencies": {
"@cypress/request": "^2.88.10",
"@cypress/request": "^2.88.11",
"@cypress/xvfb": "^1.2.4",
"@types/node": "^14.14.31",
"@types/sinonjs__fake-timers": "8.1.1",
+4 -2
View File
@@ -1901,15 +1901,17 @@ declare namespace Cypress {
* cy.screenshot()
* cy.get(".post").screenshot()
*/
screenshot(options?: Partial<Loggable & Timeoutable & ScreenshotOptions>): Chainable<null>
screenshot(options?: Partial<Loggable & Timeoutable & ScreenshotOptions>): Chainable<Subject>
/**
* Take a screenshot of the application under test and the Cypress Command Log and save under given filename.
*
* @see https://on.cypress.io/screenshot
* @example
* cy.screenshot("post-element")
* cy.get(".post").screenshot("post-element")
*/
screenshot(fileName: string, options?: Partial<Loggable & Timeoutable & ScreenshotOptions>): Chainable<null>
screenshot(fileName: string, options?: Partial<Loggable & Timeoutable & ScreenshotOptions>): Chainable<Subject>
/**
* Scroll an element into view.
+12
View File
@@ -292,6 +292,11 @@ namespace CypressCommandsTests {
return originalFn(element, text, options)
})
Cypress.Commands.overwrite<'screenshot', 'element'>('screenshot', (originalFn, subject, fileName, options) => {
subject // $ExpectType JQueryWithSelector<HTMLElement>
fileName // $ExpectType string
options // $ExpectType Partial<Loggable & Timeoutable & ScreenshotOptions> | undefined
})
Cypress.Commands.addQuery('newQuery', function(arg) {
this // $ExpectType Command
@@ -661,6 +666,9 @@ namespace CypressFilterTests {
}
namespace CypressScreenshotTests {
cy.screenshot().then((result) => {
result // $ExpectType undefined
})
cy.screenshot('example-name')
cy.screenshot('example', { log: false })
cy.screenshot({ log: false })
@@ -672,6 +680,10 @@ namespace CypressScreenshotTests {
log: true,
blackout: []
})
cy.get<HTMLDivElement>('#id').screenshot('example-name', { log: false })
cy.get<HTMLDivElement>('#id').screenshot().then((result) => {
result // $ExpectType JQuery<HTMLDivElement>
})
}
namespace CypressShadowDomTests {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "cypress",
"version": "12.16.0",
"version": "12.17.1",
"description": "Cypress is a next generation front end testing tool built for the modern web",
"private": true,
"scripts": {
@@ -70,7 +70,7 @@
"devDependencies": {
"@aws-sdk/credential-providers": "3.53.0",
"@cypress/questions-remain": "1.0.1",
"@cypress/request": "2.88.10",
"@cypress/request": "^2.88.11",
"@cypress/request-promise": "4.2.6",
"@electron/fuses": "1.6.1",
"@fellow/eslint-plugin-coffee": "0.4.13",
@@ -101,7 +101,7 @@
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { computed, ref, watchEffect } from 'vue'
import { gql, useMutation } from '@urql/vue'
import { useI18n } from '@cy/i18n'
import Switch from '@packages/frontend-shared/src/components/Switch.vue'
@@ -157,7 +157,14 @@ const switches = [
},
]
const listRef = ref(props.gql.localSettings.preferences.notifyWhenRunCompletes)
const listRef = ref()
// allow for gql value to load when navigating straight here from EnableNotificationsBanner
watchEffect(() => {
if (!listRef.value) {
listRef.value = props.gql.localSettings.preferences.notifyWhenRunCompletes
}
})
const statuses = [
{ id: 'passed', label: t('settingsPage.notifications.passed') },
@@ -1,9 +1,12 @@
import { AllowedState, defaultPreferences, Editor } from '@packages/types'
import { AllowedState, defaultPreferences, Editor, NotifyCompletionStatuses } from '@packages/types'
import pDefer from 'p-defer'
import _ from 'lodash'
import Debug from 'debug'
import type { DataContext } from '..'
const debug = Debug('cypress:data-context:actions:LocalSettingsActions')
export interface LocalSettingsApiShape {
getAvailableEditors(): Promise<Editor[]>
@@ -47,6 +50,8 @@ export class LocalSettingsActions {
return
}
debug('refresh local settings')
const dfd = pDefer<Editor[]>()
this.ctx.coreData.localSettings.refreshing = dfd.promise
@@ -60,6 +65,19 @@ export class LocalSettingsActions {
...(await this.ctx._apis.localSettingsApi.getPreferences()),
}
const preferences = this.ctx.coreData.localSettings.preferences
// Fix bad value for notifyWhenRunCompletes. See https://github.com/cypress-io/cypress/issues/27228
if (typeof preferences.notifyWhenRunCompletes === 'boolean') {
if (preferences.notifyWhenRunCompletes === true) {
preferences.notifyWhenRunCompletes = [...NotifyCompletionStatuses]
} else {
preferences.notifyWhenRunCompletes = []
}
await this.ctx._apis.localSettingsApi.setPreferences(preferences)
}
dfd.resolve()
}
}
@@ -129,6 +129,8 @@ export class VersionsDataSource {
return pkg.version
}
debug('#getLatestVersion')
const preferences = await this.ctx.localSettingsApi.getPreferences()
const notificationPreferences: ('started' | 'failing' | 'passed' | 'failed' | 'cancelled' | 'errored')[] = [
...preferences.notifyWhenRunCompletes ?? [],
@@ -0,0 +1,64 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { LocalSettingsActions } from '../../../src/actions/LocalSettingsActions'
import { createTestDataContext } from '../helper'
import type { DataContext } from '../../../src'
import { NotifyCompletionStatuses } from '@packages/types'
describe('LocalSettingsActions', () => {
let ctx: DataContext
let actions: LocalSettingsActions
beforeEach(() => {
sinon.restore()
ctx = createTestDataContext('open')
actions = new LocalSettingsActions(ctx)
})
context('refreshLocalSettings', () => {
context('notifyWhenRunCompletes', () => {
it('should fix false value', async () => {
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({
//@ts-ignore
notifyWhenRunCompletes: false,
})
await actions.refreshLocalSettings()
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql([])
})
it('should fix true value', async () => {
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({
//@ts-ignore
notifyWhenRunCompletes: true,
})
await actions.refreshLocalSettings()
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql([...NotifyCompletionStatuses])
})
it('should leave value alone if value is an array', async () => {
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({
//@ts-ignore
notifyWhenRunCompletes: ['errored'],
})
await actions.refreshLocalSettings()
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql(['errored'])
})
it('should pass through default value if not set ', async () => {
ctx._apis.localSettingsApi.getPreferences = sinon.stub().resolves({})
await actions.refreshLocalSettings()
expect(ctx.coreData.localSettings.preferences.notifyWhenRunCompletes).to.eql(['failed'])
})
})
})
})
@@ -51,6 +51,8 @@ export function createTestDataContext (mode: DataContextConfig['mode'] = 'run',
majorVersionWelcomeDismissed: { [MAJOR_VERSION_FOR_CONTENT]: 123456 },
notifyWhenRunCompletes: ['failed'],
}),
getAvailableEditors: sinon.stub(),
setPreferences: sinon.stub(),
} as unknown as LocalSettingsApiShape,
authApi: {
logIn: sinon.stub().throws('not stubbed'),
@@ -259,7 +259,7 @@ describe('src/cy/commands/files', () => {
cy.on('fail', (err) => {
const { fileLog } = this
assertLogLength(this.logs, 2)
assertLogLength(this.logs, 1)
expect(fileLog.get('error')).to.eq(err)
expect(fileLog.get('state')).to.eq('failed')
expect(err.message).to.eq(stripIndent`\
@@ -115,6 +115,23 @@ describe('privileged commands', () => {
cy.get('#basic').selectFile(Uint8Array.from([98, 97, 122]))
})
it('handles evaled code', () => {
window.eval(`
cy.task('return:arg', 'eval arg')
.then(() => {
cy.task('return:arg', 'then eval arg')
})
cy.get('body')
.each(() => {
cy.task('return:arg', 'each eval arg')
})
.within(() => {
cy.task('return:arg', 'within eval arg')
})
`)
})
it('passes in test body .then() callback', () => {
cy.then(() => {
cy.exec('echo "hello"')
+1 -1
View File
@@ -24,7 +24,7 @@
},
"devDependencies": {
"@cypress/debugging-proxy": "2.0.1",
"@cypress/request": "2.88.10",
"@cypress/request": "^2.88.11",
"@cypress/request-promise": "4.2.6",
"@packages/network": "0.0.0-development",
"@packages/ts": "0.0.0-development",
+1 -1
View File
@@ -26,7 +26,7 @@
},
"devDependencies": {
"@cypress/debugging-proxy": "2.0.1",
"@cypress/request": "2.88.10",
"@cypress/request": "^2.88.11",
"@cypress/request-promise": "4.2.6",
"@packages/https-proxy": "0.0.0-development",
"@packages/socket": "0.0.0-development",
+1 -1
View File
@@ -27,7 +27,7 @@
"utf8-stream": "0.0.0"
},
"devDependencies": {
"@cypress/request": "2.88.10",
"@cypress/request": "^2.88.11",
"@cypress/request-promise": "4.2.6",
"@cypress/sinon-chai": "2.9.1",
"@packages/resolve-dist": "0.0.0-development",
@@ -12,6 +12,7 @@ import type { ResourceType, BrowserPreRequest, BrowserResponseReceived } from '@
import type { CDPClient, ProtocolManagerShape, WriteVideoFrame } from '@packages/types'
import type { Automation } from '../automation'
import { cookieMatches, CyCookie, CyCookieFilter } from '../automation/util'
import type { CriClient } from './cri-client'
export type CdpCommand = keyof ProtocolMapping.Commands
@@ -143,6 +144,9 @@ export type SendDebuggerCommand = <T extends CdpCommand>(message: T, data?: Prot
export type OnFn = <T extends CdpEvent>(eventName: T, cb: (data: ProtocolMapping.Events[T][0]) => void) => void
type SendCloseCommand = (shouldKeepTabOpen: boolean) => Promise<any> | void
interface HasFrame {
frame: Protocol.Page.Frame
}
// the intersection of what's valid in CDP and what's valid in FFCDP
// Firefox: https://searchfox.org/mozilla-central/rev/98a9257ca2847fad9a19631ac76199474516b31e/remote/cdp/domains/parent/Network.jsm#22
@@ -157,6 +161,8 @@ const ffToStandardResourceTypeMap: { [ff: string]: ResourceType } = {
export class CdpAutomation implements CDPClient {
on: OnFn
send: SendDebuggerCommand
private frameTree: any
private gettingFrameTree: any
private constructor (private sendDebuggerCommandFn: SendDebuggerCommand, private onFn: OnFn, private sendCloseCommandFn: SendCloseCommand, private automation: Automation) {
onFn('Network.requestWillBeSent', this.onNetworkRequestWillBeSent)
@@ -281,6 +287,130 @@ export class CdpAutomation implements CDPClient {
})
}
// eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces
private _updateFrameTree = (client: CriClient, eventName) => async () => {
debugVerbose(`update frame tree for ${eventName}`)
this.gettingFrameTree = new Promise<void>(async (resolve) => {
try {
this.frameTree = (await client.send('Page.getFrameTree')).frameTree
debugVerbose('frame tree updated')
} catch (err) {
debugVerbose('failed to update frame tree:', err.stack)
} finally {
this.gettingFrameTree = null
resolve()
}
})
}
private _continueRequest = (client, params, headers?) => {
const details: Protocol.Fetch.ContinueRequestRequest = {
requestId: params.requestId,
}
if (headers && headers.length) {
// headers are received as an object but need to be an array
// to modify them
const currentHeaders = _.map(params.request.headers, (value, name) => ({ name, value }))
details.headers = [
...currentHeaders,
...headers,
]
}
debugVerbose('continueRequest: %o', details)
client.send('Fetch.continueRequest', details).catch((err) => {
// swallow this error so it doesn't crash Cypress.
// an "Invalid InterceptionId" error can randomly happen in the driver tests
// when testing the redirection loop limit, when a redirect request happens
// to be sent after the test has moved on. this shouldn't crash Cypress, in
// any case, and likely wouldn't happen for standard user tests, since they
// will properly fail and not move on like the driver tests
debugVerbose('continueRequest failed, url: %s, error: %s', params.request.url, err?.stack || err)
})
}
private _isAUTFrame = async (frameId: string) => {
debugVerbose('need frame tree')
// the request could come in while in the middle of getting the frame tree,
// which is asynchronous, so wait for it to be fetched
if (this.gettingFrameTree) {
debugVerbose('awaiting frame tree')
await this.gettingFrameTree
}
const frame = _.find(this.frameTree?.childFrames || [], ({ frame }) => {
return frame?.name?.startsWith('Your project:')
}) as HasFrame | undefined
if (frame) {
return frame.frame.id === frameId
}
return false
}
_handlePausedRequests = async (client) => {
// NOTE: only supported in chromium based browsers
await client.send('Fetch.enable')
// adds a header to the request to mark it as a request for the AUT frame
// itself, so the proxy can utilize that for injection purposes
client.on('Fetch.requestPaused', async (params: Protocol.Fetch.RequestPausedEvent) => {
const addedHeaders: {
name: string
value: string
}[] = []
/**
* Unlike the the web extension or Electrons's onBeforeSendHeaders, CDP can discern the difference
* between fetch or xhr resource types. Because of this, we set X-Cypress-Is-XHR-Or-Fetch to either
* 'xhr' or 'fetch' with CDP so the middleware can assume correct defaults in case credential/resourceTypes
* are not sent to the server.
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType
*/
if (params.resourceType === 'XHR' || params.resourceType === 'Fetch') {
debugVerbose('add X-Cypress-Is-XHR-Or-Fetch header to: %s', params.request.url)
addedHeaders.push({
name: 'X-Cypress-Is-XHR-Or-Fetch',
value: params.resourceType.toLowerCase(),
})
}
if (
// is a script, stylesheet, image, etc
params.resourceType !== 'Document'
|| !(await this._isAUTFrame(params.frameId))
) {
return this._continueRequest(client, params, addedHeaders)
}
debugVerbose('add X-Cypress-Is-AUT-Frame header to: %s', params.request.url)
addedHeaders.push({
name: 'X-Cypress-Is-AUT-Frame',
value: 'true',
})
return this._continueRequest(client, params, addedHeaders)
})
}
// we can't get the frame tree during the Fetch.requestPaused event, because
// the CDP is tied up during that event and can't be utilized. so we maintain
// a reference to it that's updated when it's likely to have been changed
_listenForFrameTreeChanges = (client) => {
debugVerbose('listen for frame tree changes')
client.on('Page.frameAttached', this._updateFrameTree(client, 'Page.frameAttached'))
client.on('Page.frameDetached', this._updateFrameTree(client, 'Page.frameDetached'))
}
onRequest = (message, data) => {
let setCookie
+11 -141
View File
@@ -8,7 +8,6 @@ import path from 'path'
import extension from '@packages/extension'
import mime from 'mime'
import { launch } from '@packages/launcher'
import type { Protocol } from 'devtools-protocol'
import appData from '../util/app_data'
import { fs } from '../util/fs'
@@ -310,142 +309,7 @@ const _handleDownloads = async function (client, downloadsFolder: string, automa
})
}
let frameTree
let gettingFrameTree
const onReconnect = (client: CriClient) => {
// if the client disconnects (e.g. due to a computer sleeping), update
// the frame tree on reconnect in cases there were changes while
// the client was disconnected
return _updateFrameTree(client, 'onReconnect')()
}
// eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces
const _updateFrameTree = (client: CriClient, eventName) => async () => {
debug(`update frame tree for ${eventName}`)
gettingFrameTree = new Promise<void>(async (resolve) => {
try {
frameTree = (await client.send('Page.getFrameTree')).frameTree
debug('frame tree updated')
} catch (err) {
debug('failed to update frame tree:', err.stack)
} finally {
gettingFrameTree = null
resolve()
}
})
}
// we can't get the frame tree during the Fetch.requestPaused event, because
// the CDP is tied up during that event and can't be utilized. so we maintain
// a reference to it that's updated when it's likely to have been changed
const _listenForFrameTreeChanges = (client) => {
debug('listen for frame tree changes')
client.on('Page.frameAttached', _updateFrameTree(client, 'Page.frameAttached'))
client.on('Page.frameDetached', _updateFrameTree(client, 'Page.frameDetached'))
}
const _continueRequest = (client, params, headers?) => {
const details: Protocol.Fetch.ContinueRequestRequest = {
requestId: params.requestId,
}
if (headers && headers.length) {
// headers are received as an object but need to be an array
// to modify them
const currentHeaders = _.map(params.request.headers, (value, name) => ({ name, value }))
details.headers = [
...currentHeaders,
...headers,
]
}
debug('continueRequest: %o', details)
client.send('Fetch.continueRequest', details).catch((err) => {
// swallow this error so it doesn't crash Cypress.
// an "Invalid InterceptionId" error can randomly happen in the driver tests
// when testing the redirection loop limit, when a redirect request happens
// to be sent after the test has moved on. this shouldn't crash Cypress, in
// any case, and likely wouldn't happen for standard user tests, since they
// will properly fail and not move on like the driver tests
debug('continueRequest failed, url: %s, error: %s', params.request.url, err?.stack || err)
})
}
interface HasFrame {
frame: Protocol.Page.Frame
}
const _isAUTFrame = async (frameId: string) => {
debug('need frame tree')
// the request could come in while in the middle of getting the frame tree,
// which is asynchronous, so wait for it to be fetched
if (gettingFrameTree) {
debug('awaiting frame tree')
await gettingFrameTree
}
const frame = _.find(frameTree?.childFrames || [], ({ frame }) => {
return frame?.name?.startsWith('Your project:')
}) as HasFrame | undefined
if (frame) {
return frame.frame.id === frameId
}
return false
}
const _handlePausedRequests = async (client) => {
await client.send('Fetch.enable')
// adds a header to the request to mark it as a request for the AUT frame
// itself, so the proxy can utilize that for injection purposes
client.on('Fetch.requestPaused', async (params: Protocol.Fetch.RequestPausedEvent) => {
const addedHeaders: {
name: string
value: string
}[] = []
/**
* Unlike the the web extension or Electrons's onBeforeSendHeaders, CDP can discern the difference
* between fetch or xhr resource types. Because of this, we set X-Cypress-Is-XHR-Or-Fetch to either
* 'xhr' or 'fetch' with CDP so the middleware can assume correct defaults in case credential/resourceTypes
* are not sent to the server.
* @see https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-ResourceType
*/
if (params.resourceType === 'XHR' || params.resourceType === 'Fetch') {
debug('add X-Cypress-Is-XHR-Or-Fetch header to: %s', params.request.url)
addedHeaders.push({
name: 'X-Cypress-Is-XHR-Or-Fetch',
value: params.resourceType.toLowerCase(),
})
}
if (
// is a script, stylesheet, image, etc
params.resourceType !== 'Document'
|| !(await _isAUTFrame(params.frameId))
) {
return _continueRequest(client, params, addedHeaders)
}
debug('add X-Cypress-Is-AUT-Frame header to: %s', params.request.url)
addedHeaders.push({
name: 'X-Cypress-Is-AUT-Frame',
value: 'true',
})
return _continueRequest(client, params, addedHeaders)
})
}
let onReconnect: (client: CriClient) => Promise<void> = async () => undefined
const _setAutomation = async (client: CriClient, automation: Automation, resetBrowserTargets: (shouldKeepTabOpen: boolean) => Promise<void>, options: BrowserLaunchOpts) => {
const cdpAutomation = await CdpAutomation.create(client.send, client.on, resetBrowserTargets, automation, options.protocolManager)
@@ -472,8 +336,6 @@ export = {
_handleDownloads,
_handlePausedRequests,
_setAutomation,
_getChromePreferences,
@@ -637,6 +499,14 @@ export = {
const cdpAutomation = await this._setAutomation(pageCriClient, automation, browserCriClient.resetBrowserTargets, options)
onReconnect = (client: CriClient) => {
// if the client disconnects (e.g. due to a computer sleeping), update
// the frame tree on reconnect in cases there were changes while
// the client was disconnected
// @ts-expect-error
return cdpAutomation._updateFrameTree(client, 'onReconnect')()
}
await pageCriClient.send('Page.enable')
await options['onInitializeNewBrowserTab']?.()
@@ -648,8 +518,8 @@ export = {
await this._navigateUsingCRI(pageCriClient, url)
await this._handlePausedRequests(pageCriClient)
_listenForFrameTreeChanges(pageCriClient)
await cdpAutomation._handlePausedRequests(pageCriClient)
cdpAutomation._listenForFrameTreeChanges(pageCriClient)
return cdpAutomation
},
+8 -55
View File
@@ -158,7 +158,7 @@ export = {
return menu.set({ withInternalDevTools: true })
}
},
async onNewWindow (this: BrowserWindow, e, url) {
async onNewWindow (this: BrowserWindow, { url }) {
let _win: BrowserWindow | null = this
_win.on('closed', () => {
@@ -168,7 +168,7 @@ export = {
})
try {
const child = await _this._launchChild(e, url, _win, projectRoot, state, options, automation)
const child = await _this._launchChild(url, _win, projectRoot, state, options, automation)
// close child on parent close
_win.on('close', () => {
@@ -218,9 +218,7 @@ export = {
return await this._launch(win, url, automation, preferences, options.videoApi, options.protocolManager)
},
_launchChild (e, url, parent, projectRoot, state, options, automation) {
e.preventDefault()
_launchChild (url, parent, projectRoot, state, options, automation) {
const [parentX, parentY] = parent.getPosition()
const electronOptions = this._defaultOptions(projectRoot, state, options, automation)
@@ -237,10 +235,6 @@ export = {
const win = Windows.create(projectRoot, electronOptions)
// needed by electron since we prevented default and are creating
// our own BrowserWindow (https://electron.atom.io/docs/api/web-contents/#event-new-window)
e.newGuest = win
return this._launch(win, url, automation, electronOptions)
},
@@ -289,6 +283,8 @@ export = {
this._clearCache(win.webContents),
])
await browserCriClient?.currentlyAttachedTarget?.send('Page.enable')
await Promise.all([
protocolManager?.connectToBrowser(cdpAutomation),
videoApi && recordVideo(cdpAutomation, videoApi),
@@ -299,7 +295,9 @@ export = {
await this._enableDebugger()
await win.loadURL(url)
this._listenToOnBeforeHeaders(win)
await cdpAutomation._handlePausedRequests(browserCriClient?.currentlyAttachedTarget)
cdpAutomation._listenForFrameTreeChanges(browserCriClient?.currentlyAttachedTarget)
return win
},
@@ -341,51 +339,6 @@ export = {
})
},
_listenToOnBeforeHeaders (win: BrowserWindow) {
// true if the frame only has a single parent, false otherwise
const isFirstLevelIFrame = (frame) => (!!frame?.parent && !frame.parent.parent)
// adds a header to the request to mark it as a request for the AUT frame
// itself, so the proxy can utilize that for injection purposes
win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => {
const requestModifications = {
requestHeaders: {
...details.requestHeaders,
/**
* Unlike CDP, Electrons's onBeforeSendHeaders resourceType cannot discern the difference
* between fetch or xhr resource types, but classifies both as 'xhr'. Because of this,
* we set X-Cypress-Is-XHR-Or-Fetch to true if the request is made with 'xhr' or 'fetch' so the
* middleware doesn't incorrectly assume which request type is being sent
* @see https://www.electronjs.org/docs/latest/api/web-request#webrequestonbeforesendheadersfilter-listener
*/
...(details.resourceType === 'xhr') ? {
'X-Cypress-Is-XHR-Or-Fetch': 'true',
} : {},
},
}
if (
// isn't an iframe
details.resourceType !== 'subFrame'
// the top-level frame or a nested frame
|| !isFirstLevelIFrame(details.frame)
// is the spec frame, not the AUT
|| details.url.includes('__cypress')
) {
cb(requestModifications)
return
}
cb({
requestHeaders: {
...requestModifications.requestHeaders,
'X-Cypress-Is-AUT-Frame': 'true',
},
})
})
},
_getPartition (options) {
if (options.isTextTerminal) {
// create dynamic persisted run
@@ -8,6 +8,7 @@ import path from 'path'
import pid from 'pidusage'
import { groupCyProcesses, Process } from '../../util/process_profiler'
import browsers from '..'
import { telemetry } from '@packages/telemetry/src/browser'
import type { Automation } from '../../automation'
import type { BrowserInstance } from '../types'
@@ -274,10 +275,15 @@ const checkMemoryPressureAndLog = async ({ automation, test }: { automation: Aut
const checkMemoryPressure: (automation: Automation) => Promise<void> = measure(async (automation: Automation) => {
if (collectGarbageOnNextTest) {
debug('forcing garbage collection')
let span
try {
span = telemetry.startSpan({ name: 'checkMemoryPressure:collect:garbage' })
await automation.request('collect:garbage', null, null)
} catch (err) {
debug('error collecting garbage: %o', err)
} finally {
span?.end()
}
} else {
debug('skipping garbage collection')
+11 -3
View File
@@ -17,7 +17,7 @@ export type WindowOptions = Electron.BrowserWindowConstructorOptions & {
*/
trackState?: TrackStateMap
onFocus?: () => void
onNewWindow?: (e, url, frameName, disposition, options) => Promise<void>
onNewWindow?: ({ disposition, features, frameName, postBody, referrer, url }) => Promise<void>
onCrashed?: () => void
}
@@ -189,8 +189,16 @@ export function create (projectRoot, _options: WindowOptions, newBrowserWindow =
return options.onCrashed.apply(win, args)
})
win.webContents.on('new-window', function (...args) {
return options.onNewWindow.apply(win, args)
// As of Electron v22, the 'new-window' event has been removed for 'setWindowOpenHandler'.
// @see https://github.com/electron/electron/blob/v21.0.0/docs/api/web-contents.md#event-new-window-deprecated
// @see https://github.com/electron/electron/pull/34526
win.webContents.setWindowOpenHandler(function (...args) {
// opens the child window from the root window so Cypress can decorate it with needed events and configuration
options.onNewWindow.apply(win, args)
// Because the opening of the window is handled by the root window above, deny opening the window.
// @see https://github.com/electron/electron/blob/v21.0.0/docs/api/web-contents.md#contentssetwindowopenhandlerhandler
return { action: 'deny' }
})
if (options.trackState) {
@@ -30,6 +30,8 @@
const queryStringRegex = /\?.*$/
let hasValidCallbackContext = false
// since this function is eval'd, the scripts are included as stringified JSON
if (scripts) {
scripts = parse(scripts)
@@ -69,6 +71,18 @@
return filteredLines.length > 0
}
const isInCallback = (err) => {
return stringIncludes.call(err.stack, 'thenFn@') || stringIncludes.call(err.stack, 'withinFn@')
}
const hasCallbackInsideEval = (err) => {
if (browserFamily === 'webkit') {
return isInCallback(err) && hasValidCallbackContext
}
return isInCallback(err) && stringIncludes.call(err.stack, '> eval line')
}
// in non-chromium browsers, the stack will include either the spec file url
// or the support file
const hasStackLinesFromSpecOrSupportFile = (err) => {
@@ -96,16 +110,22 @@
'task',
]
const callbackCommands = [
'each',
'then',
'within',
]
function stackIsFromSpecFrame (err) {
if (isSpecBridge) {
return hasSpecBridgeInvocation(err)
}
if (browserFamily === 'chromium') {
return hasSpecFrameStackLines(err)
return hasStackLinesFromSpecOrSupportFile(err) || hasSpecFrameStackLines(err)
}
return hasStackLinesFromSpecOrSupportFile(err)
return hasCallbackInsideEval(err) || hasStackLinesFromSpecOrSupportFile(err)
}
// source: https://github.com/bryc/code/blob/d0dac1c607a005679799024ff66166e13601d397/jshash/experimental/cyrb53.js
@@ -141,8 +161,6 @@
}
async function onCommandInvocation (command) {
if (!arrayIncludes.call(privilegedCommands, command.name)) return
// message doesn't really matter since we're only interested in the stack
const err = new Err('command stack error')
@@ -152,6 +170,12 @@
captureStackTrace.call(Err, err, onCommandInvocation)
}
if (arrayIncludes.call(callbackCommands, command.name)) {
hasValidCallbackContext = stackIsFromSpecFrame(err)
}
if (!arrayIncludes.call(privilegedCommands, command.name)) return
// if stack is not validated as being from the spec frame, don't add
// it as a verified command
if (!stackIsFromSpecFrame(err)) return
+8 -1
View File
@@ -24,9 +24,16 @@ const getRemoteDebuggingPort = () => {
const setRemoteDebuggingPort = async () => {
try {
const port = await getPort()
const { app } = require('electron')
// if port was already set via passing from environment variable ELECTRON_EXTRA_LAUNCH_ARGS,
// then just keep the supplied value
if (app.commandLine.getSwitchValue('remote-debugging-port')) {
return
}
const port = await getPort()
// set up remote debugging port
app.commandLine.appendSwitch('remote-debugging-port', String(port))
} catch (err) {
+1 -1
View File
@@ -26,7 +26,7 @@
"@benmalka/foxdriver": "0.4.1",
"@cypress/commit-info": "2.2.0",
"@cypress/get-windows-proxy": "1.6.2",
"@cypress/request": "2.88.10",
"@cypress/request": "^2.88.11",
"@cypress/request-promise": "4.2.6",
"@cypress/vite-dev-server": "0.0.0-development",
"@cypress/webpack-batteries-included-preprocessor": "0.0.0-development",
@@ -1007,6 +1007,11 @@ describe('lib/cypress', () => {
close: sinon.stub().resolves(),
}
const cdpAutomation = {
_handlePausedRequests: sinon.stub().resolves(),
_listenForFrameTreeChanges: sinon.stub().returns(),
}
sinon.stub(chromeBrowser, '_writeExtension').resolves()
sinon.stub(BrowserCriClient, 'create').resolves(browserCriClient)
@@ -1017,7 +1022,7 @@ describe('lib/cypress', () => {
sinon.stub(chromeBrowser, '_handleDownloads').resolves()
sinon.stub(chromeBrowser, '_recordVideo').resolves()
sinon.stub(chromeBrowser, '_setAutomation').returns()
sinon.stub(chromeBrowser, '_setAutomation').returns(cdpAutomation)
return cypress.start([
`--run-project=${this.pluginBrowser}`,
@@ -1049,6 +1054,9 @@ describe('lib/cypress', () => {
expect(BrowserCriClient.create).to.have.been.calledOnce
expect(browserCriClient.attachToTargetUrl).to.have.been.calledOnce
expect(cdpAutomation._handlePausedRequests).to.have.been.calledOnce
expect(cdpAutomation._listenForFrameTreeChanges).to.have.been.calledOnce
})
})
@@ -1062,6 +1070,7 @@ describe('lib/cypress', () => {
const browserCriClient = {
ensureMinimumProtocolVersion: sinon.stub().resolves(),
attachToTargetUrl: sinon.stub().resolves(criClient),
currentlyAttachedTarget: criClient,
close: sinon.stub().resolves(),
}
@@ -17,6 +17,7 @@ module.exports = {
exit () {},
commandLine: {
appendSwitch () {},
getSwitchValue () {},
appendArgument () {},
},
disableHardwareAcceleration () {},
@@ -346,177 +346,178 @@ describe('lib/browsers/electron', () => {
})
})
describe('adding header aut iframe requests', function () {
it('does not add header if not a sub frame', function () {
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
return electron._launch(this.win, this.url, this.automation, this.options)
.then(() => {
const details = {
resourceType: 'stylesheet',
}
const cb = sinon.stub()
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
expect(cb).to.be.calledOnce
expect(cb).to.be.calledWith({
requestHeaders: {},
})
})
})
it('does not add header if it is the top frame', function () {
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
return electron._launch(this.win, this.url, this.automation, this.options)
.then(() => {
const details = {
resourceType: 'subFrame',
frame: {
parent: null,
},
}
const cb = sinon.stub()
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
expect(cb).to.be.calledOnce
expect(cb).to.be.calledWith({
requestHeaders: {},
})
})
})
it('does not add header if it is a nested frame', function () {
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
return electron._launch(this.win, this.url, this.automation, this.options)
.then(() => {
const details = {
resourceType: 'subFrame',
frame: {
parent: {
parent: {
parent: null,
describe('adding header to AUT iframe request', function () {
beforeEach(function () {
const frameTree = {
frameTree: {
childFrames: [
{
frame: {
id: 'aut-frame-id',
name: 'Your project: "FakeBlock"',
},
},
},
}
const cb = sinon.stub()
{
frame: {
id: 'spec-frame-id',
name: 'Your Spec: "spec.js"',
},
},
],
},
}
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
this.pageCriClient.send.withArgs('Page.getFrameTree').resolves(frameTree)
})
expect(cb).to.be.calledOnce
expect(cb).to.be.calledWith({
requestHeaders: {},
})
it('sends Fetch.enable', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
expect(this.pageCriClient.send).to.have.been.calledWith('Fetch.enable')
})
it('does not add header when not a document', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
this.pageCriClient.on.withArgs('Fetch.requestPaused').yield({
requestId: '1234',
resourceType: 'Script',
})
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
requestId: '1234',
})
})
it('does not add header if it is a spec frame request', function () {
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
it('does not add header when it is a spec frame request', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
return electron._launch(this.win, this.url, this.automation, this.options)
.then(() => {
const details = {
resourceType: 'subFrame',
frame: {
parent: {
parent: null,
},
},
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
frameId: 'spec-frame-id',
requestId: '1234',
resourceType: 'Document',
request: {
url: '/__cypress/integration/spec.js',
}
const cb = sinon.stub()
},
})
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
expect(cb).to.be.calledWith({
requestHeaders: {},
})
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
requestId: '1234',
})
})
it('does not add header if frame is not available', function () {
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
it('appends X-Cypress-Is-AUT-Frame header to AUT iframe request', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
return electron._launch(this.win, this.url, this.automation, this.options)
.then(() => {
const details = {
resourceType: 'subFrame',
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
frameId: 'aut-frame-id',
requestId: '1234',
resourceType: 'Document',
request: {
url: 'http://localhost:3000/index.html',
}
const cb = sinon.stub()
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
expect(cb).to.be.calledWith({
requestHeaders: {},
})
})
})
it('adds X-Cypress-Is-AUT-Frame header to AUT iframe request', function () {
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
return electron._launch(this.win, this.url, this.automation, this.options)
.then(() => {
const details = {
resourceType: 'subFrame',
frame: {
parent: {
parent: null,
},
},
url: 'http://localhost:3000/index.html',
requestHeaders: {
headers: {
'X-Foo': 'Bar',
},
}
const cb = sinon.stub()
},
})
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
expect(cb).to.be.calledOnce
expect(cb).to.be.calledWith({
requestHeaders: {
'X-Foo': 'Bar',
'X-Cypress-Is-AUT-Frame': 'true',
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
requestId: '1234',
headers: [
{
name: 'X-Foo',
value: 'Bar',
},
})
{
name: 'X-Cypress-Is-AUT-Frame',
value: 'true',
},
],
})
})
it('adds X-Cypress-Is-XHR-Or-Fetch header if xhr request (includes fetch)', function () {
sinon.stub(this.win.webContents.session.webRequest, 'onBeforeSendHeaders')
it('appends X-Cypress-Is-XHR-Or-Fetch header to fetch request', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
return electron._launch(this.win, this.url, this.automation, this.options)
.then(() => {
const details = {
resourceType: 'xhr',
frame: {
parent: {
parent: null,
},
},
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
frameId: 'aut-frame-id',
requestId: '1234',
resourceType: 'Fetch',
request: {
url: 'http://localhost:3000/test-request',
requestHeaders: {
headers: {
'X-Foo': 'Bar',
},
}
const cb = sinon.stub()
this.win.webContents.session.webRequest.onBeforeSendHeaders.lastCall.args[0](details, cb)
expect(cb).to.be.calledOnce
expect(cb).to.be.calledWith({
requestHeaders: {
'X-Foo': 'Bar',
'X-Cypress-Is-XHR-Or-Fetch': 'true',
},
})
},
})
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
requestId: '1234',
headers: [
{
name: 'X-Foo',
value: 'Bar',
},
{
name: 'X-Cypress-Is-XHR-Or-Fetch',
value: 'fetch',
},
],
})
})
it('appends X-Cypress-Is-XHR-Or-Fetch header to xhr request', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
await this.pageCriClient.on.withArgs('Fetch.requestPaused').args[0][1]({
frameId: 'aut-frame-id',
requestId: '1234',
resourceType: 'XHR',
request: {
url: 'http://localhost:3000/test-request',
headers: {
'X-Foo': 'Bar',
},
},
})
expect(this.pageCriClient.send).to.be.calledWith('Fetch.continueRequest', {
requestId: '1234',
headers: [
{
name: 'X-Foo',
value: 'Bar',
},
{
name: 'X-Cypress-Is-XHR-Or-Fetch',
value: 'xhr',
},
],
})
})
it('gets frame tree on Page.frameAttached', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
this.pageCriClient.on.withArgs('Page.frameAttached').yield()
expect(this.pageCriClient.send).to.be.calledWith('Page.getFrameTree')
})
it('gets frame tree on Page.frameDetached', async function () {
await electron._launch(this.win, this.url, this.automation, this.options)
this.pageCriClient.on.withArgs('Page.frameDetached').yield()
expect(this.pageCriClient.send).to.be.calledWith('Page.getFrameTree')
})
})
})
@@ -797,19 +798,16 @@ describe('lib/browsers/electron', () => {
return sinon.stub(electron, '_launchChild').resolves(this.win)
})
it('passes along event, url, parent window and options', function () {
it('passes along url, parent window and options', function () {
const opts = electron._defaultOptions(this.options.projectRoot, this.state, this.options, this.automation)
const event = {}
const parentWindow = {
on: sinon.stub(),
}
opts.onNewWindow.call(parentWindow, event, this.url)
opts.onNewWindow.call(parentWindow, { url: this.url })
expect(electron._launchChild).to.be.calledWith(
event, this.url, parentWindow, this.options.projectRoot, this.state, this.options, this.automation,
)
expect(electron._launchChild).to.be.calledWith(this.url, parentWindow, this.options.projectRoot, this.state, this.options, this.automation)
})
it('adds pid of new BrowserWindow to allPids list', function () {
@@ -23,6 +23,7 @@ describe('lib/gui/windows', () => {
this.win.getPosition = sinon.stub().returns([3, 4])
this.win.webContents = new EventEmitter()
this.win.webContents.openDevTools = sinon.stub()
this.win.webContents.setWindowOpenHandler = sinon.stub()
this.win.webContents.userAgent = DEFAULT_USER_AGENT
this.win.isDestroyed = sinon.stub().returns(false)
})
@@ -0,0 +1,36 @@
const electronApp = require('../../../lib/util/electron-app')
describe('/lib/util/electron-app', () => {
context('remote debugging port', () => {
beforeEach(() => {
sinon.restore()
})
it('should not override port if previously set', async () => {
const { app } = require('electron')
sinon.stub(app.commandLine, 'appendSwitch')
sinon.stub(app.commandLine, 'getSwitchValue').callsFake((args) => {
return '4567'
})
await electronApp.setRemoteDebuggingPort()
expect(app.commandLine.appendSwitch).to.not.have.been.called
})
it('should assign random port if not previously set', async () => {
const { app } = require('electron')
sinon.stub(app.commandLine, 'appendSwitch')
sinon.stub(app.commandLine, 'getSwitchValue').callsFake((args) => {
return undefined
})
await electronApp.setRemoteDebuggingPort()
expect(app.commandLine.appendSwitch).to.have.been.calledWith('remote-debugging-port', sinon.match.string)
})
})
})
+3 -1
View File
@@ -1,6 +1,8 @@
import type { BannersState, Editor, MajorVersionWelcomeDismissed } from '.'
export type NotifyWhenRunCompletes = 'passed' | 'failed' | 'cancelled' | 'errored'
export const NotifyCompletionStatuses = ['passed', 'failed', 'cancelled', 'errored'] as const
export type NotifyWhenRunCompletes = typeof NotifyCompletionStatuses[number]
export const defaultPreferences: AllowedState = {
autoScrollingEnabled: true,
+2 -17
View File
@@ -16,8 +16,8 @@
"renovate"
],
"commitMessageSuffix": "🌟",
"prHourlyLimit": 2,
"prConcurrentLimit": 5,
"prHourlyLimit": 1,
"prConcurrentLimit": 1,
"updateNotScheduled": false,
"timezone": "America/New_York",
"schedule": [
@@ -31,21 +31,6 @@
"semanticCommitType": "dependency",
"groupName": "electron",
"dependencyDashboardApproval": false
},
{
"excludePackagePatterns": [
"^electron"
],
"semanticCommitType": "dependency",
"dependencyDashboardApproval": true
},
{
"matchDepTypes": [
"dependencies",
"require"
],
"semanticCommitType": "dependency",
"dependencyDashboardApproval": true
}
]
}
-2
View File
@@ -69,8 +69,6 @@ async function prepareCircleCache () {
.replace(/(.*?)\/node_modules/, '$1_node_modules')
.replace(BASE_DIR, CACHE_DIR)
// self-hosted M1 doesn't always clear this directory between runs, so remove it
await fsExtra.remove(dest)
await fsExtra.move(src, dest)
}),
)
+1 -1
View File
@@ -22,7 +22,7 @@
"@babel/preset-env": "7.9.0",
"@cypress/commit-info": "2.2.0",
"@cypress/debugging-proxy": "2.0.1",
"@cypress/request": "2.88.10",
"@cypress/request": "^2.88.11",
"@cypress/request-promise": "4.2.6",
"@cypress/sinon-chai": "2.9.1",
"@cypress/webpack-preprocessor": "0.0.0-development",
+9 -9
View File
@@ -2338,10 +2338,10 @@
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
"@cypress/request@2.88.10", "@cypress/request@^2.88.10":
version "2.88.10"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==
"@cypress/request@^2.88.11":
version "2.88.11"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.11.tgz#5a4c7399bc2d7e7ed56e92ce5acb620c8b187047"
integrity sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@@ -2356,7 +2356,7 @@
json-stringify-safe "~5.0.1"
mime-types "~2.1.19"
performance-now "^2.1.0"
qs "~6.5.2"
qs "~6.10.3"
safe-buffer "^5.1.2"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
@@ -24437,10 +24437,10 @@ qs@6.9.7:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
qs@^6.4.0, qs@^6.5.1, qs@^6.9.4:
version "6.10.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
qs@^6.4.0, qs@^6.5.1, qs@^6.9.4, qs@~6.10.3:
version "6.10.5"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4"
integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==
dependencies:
side-channel "^1.0.4"