mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-13 02:40:22 -05:00
Merge branch 'develop' into mschile/v13_merge_develop
This commit is contained in:
+14
-32
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
Vendored
+4
-2
@@ -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.
|
||||
|
||||
@@ -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
@@ -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"')
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user