Merge branch 'develop' into release/13.0.0

This commit is contained in:
Ryan Manuel
2023-08-25 09:02:04 -05:00
committed by GitHub
36 changed files with 1247 additions and 749 deletions

View File

@@ -4,9 +4,8 @@ name: Snyk Software Composition Analysis Scan
# "develop" branch. We use this as a control to prevent vulnerable packages
# from being introduced into the codebase.
on:
pull_request_target:
types:
- opened
pull_request:
types: [opened, edited]
branches:
- develop
jobs:

View File

@@ -26,6 +26,10 @@ _Released 08/29/2023 (PENDING)_
**Bugfixes:**
- Only force CommonJS when running `ts-node` with a `TS_NODE_COMPILER` environment variable, such as when Cypress uses `ts-node` internally. This solves an issue where Cypress' internal `tsconfig` conflicts with properties set in the user's `tsconfig.json` such as `module` and `moduleResolution`. Fixes [#26308](https://github.com/cypress-io/cypress/issues/26308) and [#27448](https://github.com/cypress-io/cypress/issues/27448).
- Clarified Svelte 4 works correctly with Component Testing and updated dependencies checks to reflect this. It was incorrectly flagged as not supported. Fixes [#27465](https://github.com/cypress-io/cypress/issues/27465).
- Resolve the `process/browser` global inside `@cypress/webpack-batteries-included-preprocessor` to resolve to `process/browser.js` in order to explicitly provide the file extension. File resolution must include the extension for `.mjs` and `.js` files inside ESM packages in order to resolve correctly. Fixes[#27599](https://github.com/cypress-io/cypress/issues/27599).
- Fixed an issue where the correct `pnp` process was not being discovered. Fixes [#27562](https://github.com/cypress-io/cypress/issues/27562).
- Fixed incorrect type declarations for Cypress and Chai globals that asserted them to be local variables of the global scope rather than properties on the global object. Fixes [#27539](https://github.com/cypress-io/cypress/issues/27539). Fixed in [#27540](https://github.com/cypress-io/cypress/pull/27540).
## 12.17.4
@@ -226,11 +230,11 @@ _Released 04/17/2023_
**Bugfixes:**
- Capture the [Azure](https://azure.microsoft.com/) CI provider's environment variable [`SYSTEM_PULLREQUEST_PULLREQUESTNUMBER`](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#system-variables-devops-services) to display the linked PR number in the Cloud. Addressed in [#26215](https://github.com/cypress-io/cypress/pull/26215).
- Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777) and [#26388](https://github.com/cypress-io/cypress/issues/26388).
- Updated to use the `SEMAPHORE_GIT_WORKING_BRANCH` [Semphore](https://docs.semaphoreci.com) CI environment variable to correctly associate a Cloud run to the current branch. Previously this was incorrectly associating a run to the target branch. Fixes [#26309](https://github.com/cypress-io/cypress/issues/26309).
- Fix an edge case in Component Testing where a custom `baseUrl` in `tsconfig.json` for Next.js 13.2.0+ is not respected. This was partially fixed in [#26005](https://github.com/cypress-io/cypress/pull/26005), but an edge case was missed. Fixes [#25951](https://github.com/cypress-io/cypress/issues/25951).
- Fixed an issue where `click` events fired on `.type('{enter}')` did not propagate through shadow roots. Fixes [#26392](https://github.com/cypress-io/cypress/issues/26392).
- Capture the [Azure](https://azure.microsoft.com/) CI provider's environment variable [`SYSTEM_PULLREQUEST_PULLREQUESTNUMBER`](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#system-variables-devops-services) to display the linked PR number in the Cloud. Addressed in [#26215](https://github.com/cypress-io/cypress/pull/26215).
- Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777) and [#26388](https://github.com/cypress-io/cypress/issues/26388).
- Updated to use the `SEMAPHORE_GIT_WORKING_BRANCH` [Semphore](https://docs.semaphoreci.com) CI environment variable to correctly associate a Cloud run to the current branch. Previously this was incorrectly associating a run to the target branch. Fixes [#26309](https://github.com/cypress-io/cypress/issues/26309).
- Fix an edge case in Component Testing where a custom `baseUrl` in `tsconfig.json` for Next.js 13.2.0+ is not respected. This was partially fixed in [#26005](https://github.com/cypress-io/cypress/pull/26005), but an edge case was missed. Fixes [#25951](https://github.com/cypress-io/cypress/issues/25951).
- Fixed an issue where `click` events fired on `.type('{enter}')` did not propagate through shadow roots. Fixes [#26392](https://github.com/cypress-io/cypress/issues/26392).
**Misc:**
@@ -252,16 +256,16 @@ _Released 03/28/2023_
**Bugfixes:**
- Fixed a compatibility issue so that component test projects can use [Vite](https://vitejs.dev/) version 4.2.0 and greater. Fixes [#26138](https://github.com/cypress-io/cypress/issues/26138).
- Fixed an issue where [`cy.intercept()`](https://docs.cypress.io/api/commands/intercept) added an additional `content-length` header to spied requests that did not set a `content-length` header on the original request. Fixes [#24407](https://github.com/cypress-io/cypress/issues/24407).
- Changed the way that Git hashes are loaded so that non-relevant runs are excluded from the Debug page. Fixes [#26058](https://github.com/cypress-io/cypress/issues/26058).
- Corrected the [`.type()`](https://docs.cypress.io/api/commands/type) command to account for shadow root elements when determining whether or not focus needs to be simulated before typing. Fixes [#26198](https://github.com/cypress-io/cypress/issues/26198).
- Fixed an issue where an incorrect working directory could be used for Git operations on Windows. Fixes [#23317](https://github.com/cypress-io/cypress/issues/23317).
- Capture the [Buildkite](https://buildkite.com/) CI provider's environment variable `BUILDKITE_RETRY_COUNT` to handle CI retries in the Cloud. Addressed in [#25750](https://github.com/cypress-io/cypress/pull/25750).
- Fixed a compatibility issue so that component test projects can use [Vite](https://vitejs.dev/) version 4.2.0 and greater. Fixes [#26138](https://github.com/cypress-io/cypress/issues/26138).
- Fixed an issue where [`cy.intercept()`](https://docs.cypress.io/api/commands/intercept) added an additional `content-length` header to spied requests that did not set a `content-length` header on the original request. Fixes [#24407](https://github.com/cypress-io/cypress/issues/24407).
- Changed the way that Git hashes are loaded so that non-relevant runs are excluded from the Debug page. Fixes [#26058](https://github.com/cypress-io/cypress/issues/26058).
- Corrected the [`.type()`](https://docs.cypress.io/api/commands/type) command to account for shadow root elements when determining whether or not focus needs to be simulated before typing. Fixes [#26198](https://github.com/cypress-io/cypress/issues/26198).
- Fixed an issue where an incorrect working directory could be used for Git operations on Windows. Fixes [#23317](https://github.com/cypress-io/cypress/issues/23317).
- Capture the [Buildkite](https://buildkite.com/) CI provider's environment variable `BUILDKITE_RETRY_COUNT` to handle CI retries in the Cloud. Addressed in [#25750](https://github.com/cypress-io/cypress/pull/25750).
**Misc:**
- Made some minor styling updates to the Debug page. Addresses [#26041](https://github.com/cypress-io/cypress/issues/26041).
- Made some minor styling updates to the Debug page. Addresses [#26041](https://github.com/cypress-io/cypress/issues/26041).
## 12.8.1
@@ -331,7 +335,7 @@ _Released 02/24/2023_
**Misc:**
- Made updates to the way that the Debug Page header displays information. Addresses [#25796](https://github.com/cypress-io/cypress/issues/25796) and [#25798](https://github.com/cypress-io/cypress/issues/25798).
- Made updates to the way that the Debug Page header displays information. Addresses [#25796](https://github.com/cypress-io/cypress/issues/25796) and [#25798](https://github.com/cypress-io/cypress/issues/25798).
## 12.6.0

View File

@@ -1,3 +1,3 @@
// Cypress adds chai expect and assert to global
declare const expect: Chai.ExpectStatic
declare const assert: Chai.AssertStatic
declare var expect: Chai.ExpectStatic
declare var assert: Chai.AssertStatic

View File

@@ -7,7 +7,7 @@ cy.get('button').click()
cy.get('.result').contains('Expected text')
```
*/
declare const cy: Cypress.cy & CyEventEmitter
declare var cy: Cypress.cy & CyEventEmitter
/**
* Global variable `Cypress` holds common utilities and constants.
@@ -19,4 +19,4 @@ Cypress.version // => "1.4.0"
Cypress._ // => Lodash _
```
*/
declare const Cypress: Cypress.Cypress & CyEventEmitter
declare var Cypress: Cypress.Cypress & CyEventEmitter

View File

@@ -1201,3 +1201,20 @@ namespace CypressRequireTests {
Cypress.require({}) // $ExpectError
Cypress.require(123) // $ExpectError
}
namespace CypressGlobalsTests {
Cypress
cy
expect
assert
window.Cypress
window.cy
window.expect
window.assert
globalThis.Cypress
globalThis.cy
globalThis.expect
globalThis.assert
}

View File

@@ -135,7 +135,15 @@ const getDefaultWebpackOptions = () => {
plugins: [
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser',
// As of Webpack 5, a new option called resolve.fullySpecified, was added.
// This option means that a full path, in particular to .mjs / .js files
// in ESM packages must have the full path of an import specified.
// Otherwise, compilation fails as this option defaults to true.
// This means we need to adjust our global injections to always
// resolve to include the full file extension if a file resolution is provided.
// @see https://github.com/cypress-io/cypress/issues/27599
// @see https://webpack.js.org/configuration/module/#resolvefullyspecified
process: 'process/browser.js',
}),
],
resolve: {
@@ -163,7 +171,7 @@ const getDefaultWebpackOptions = () => {
path: require.resolve('path-browserify'),
perf_hooks: false,
punycode: require.resolve('punycode/'),
process: require.resolve('process/browser'),
process: require.resolve('process/browser.js'),
querystring: require.resolve('querystring-es3'),
readline: false,
repl: false,

View File

@@ -1,3 +1,8 @@
import { expect } from 'chai'
// make sure built in resolves correctly in mjs through the provide plugin, including process & buffer
process.env.foo = 'bar'
const buffer = new Buffer('foo');
expect(true).to.be.true

View File

@@ -186,7 +186,8 @@ describe('SelectorPlayground', () => {
})
})
it('shows tooltips when buttons are focused', () => {
// TODO: fix this flaky test
it.skip('shows tooltips when buttons are focused', () => {
mountSelectorPlayground()
cy.get('[data-cy="playground-toggle"]').focus()

View File

@@ -0,0 +1,48 @@
describe('a11y - click targets', () => {
beforeEach(() => {
cy.visit('/fixtures/a11y/click-targets.html')
})
context('button - handles click behavior', () => {
it('semantic', () => {
cy.get('#test-a11y-semantic-button').click()
cy.get('#test-a11y-click-result').should('have.text', 'Click - SEMANTIC Button')
})
it('styled div', () => {
cy.get('#test-a11y-styled-button').click()
cy.get('#test-a11y-click-result').should('have.text', 'Click - STYLED Button')
})
it('role', () => {
cy.get('#test-a11y-role-button').click()
cy.get('#test-a11y-click-result').should('have.text', 'Click - ROLE Button')
cy.get('#test-a11y-role-button').type('{enter}')
cy.get('#test-a11y-click-result').should('have.text', 'Keydown - Enter - ROLE Button')
cy.get('#test-a11y-role-button').type(' ')
cy.get('#test-a11y-click-result').should('have.text', 'Keydown - Space - ROLE Button')
})
})
context('link', () => {
it('semantic', () => {
cy.get('#test-a11y-semantic-link').click()
cy.get('#test-a11y-click-result').should('have.text', 'Click - SEMANTIC Link')
})
it('styled div', () => {
cy.get('#test-a11y-styled-link').click()
cy.get('#test-a11y-click-result').should('have.text', 'Click - STYLED Link')
})
it('role', () => {
cy.get('#test-a11y-role-link').click()
cy.get('#test-a11y-click-result').should('have.text', 'Click - ROLE Link')
cy.get('#test-a11y-role-link').type('{enter}')
cy.get('#test-a11y-click-result').should('have.text', 'Keydown - Enter - ROLE Link')
})
})
})

View File

@@ -0,0 +1,35 @@
describe('a11y - inputs', () => {
beforeEach(() => {
cy.visit('/fixtures/a11y/inputs.html')
})
context('checkbox - handles activation', () => {
it('semantic', () => {
cy.get('#test-a11y-semantic-checkbox').click().should('have.value', 'on')
})
it('styled div', () => {
cy.get('#test-a11y-styled-checkbox').click().should('have.class', 'checked')
})
it('role', () => {
cy.get('#test-a11y-role-checkbox').click().should('have.attr', 'aria-checked').and('equal', 'true')
cy.get('#test-a11y-role-checkbox').type(' ').should('have.attr', 'aria-checked').and('equal', 'false')
})
})
context('text - handles text entry', () => {
it('semantic', () => {
cy.get('#test-a11y-semantic-text').type('test value').should('have.value', 'test value')
})
it('styled div', () => {
cy.get('#test-a11y-styled-text').type('test value').should('have.text', 'test value')
})
it('role', () => {
cy.get('#test-a11y-role-text').type('test value').should('have.text', 'test value')
})
})
})

View File

@@ -0,0 +1,31 @@
describe('a11y - slider', () => {
beforeEach(() => {
cy.visit('/fixtures/a11y/slider.html')
})
context('slider - handles activation', () => {
it('semantic', () => {
cy.get('#test-a11y-semantic-slider').as('range')
.focus()
.invoke('val', 1)
.trigger('change')
.should('have.value', '1')
})
it('styled div', () => {
cy.get('#test-a11y-styled-slider-container').click(20, 4)
})
it('role', () => {
cy.get('#test-a11y-role-slider-container')
.click(20, 4)
.should('have.attr', 'aria-valuenow')
.and('equal', '1')
cy.get('#test-a11y-role-slider-container')
.type('{rightArrow}')
.should('have.attr', 'aria-valuenow')
.and('equal', '2')
})
})
})

View File

@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html>
<head>
<title>A11y Test Bed - Click Targets</title>
<style>
.styled-test-button {
appearance: auto;
background-color: buttonface;
border-color: buttonborder;
border-radius: 3px;
border-style: outset;
border-width: 1px;
box-sizing: border-box;
display: inline-block;
cursor: default;
font-family: Arial, Helvetica, sans-serif;
font-size: 13px;
margin: 0em;
padding: 2px 6px;
text-align: center;
line-height: normal;
user-select: none;
}
.styled-test-button:hover {
background-color: #ddd;
}
.styled-test-button:active {
background-color: #f7f7f7;
}
.styled-test-link {
color: blue;
cursor: pointer;
display: inline-block;
text-decoration: underline;
}
.styled-test-link:active {
color: #ff5100;
}
</style>
</head>
<body>
<div>
<p>
Last Action: <span id="test-a11y-click-result">Blank</span>
</p>
<!-- Semantic Button -->
<button
type="button"
id="test-a11y-semantic-button"
>
Semantic
</button>
<!-- Styled Button -->
<div
id="test-a11y-styled-button"
class="styled-test-button"
>
Styled
</div>
<!-- Role Button -->
<div
type="button"
role="button"
tabIndex="0"
id="test-a11y-role-button"
class="styled-test-button"
>
Role
</div>
<hr />
<!-- Semantic Link -->
<a
href="#"
id="test-a11y-semantic-link"
>
Semantic
</a>
<!-- Styled Link -->
<div
class="styled-test-link"
id="test-a11y-styled-link"
>
Styled
</div>
<!-- Role Link -->
<div
role="link"
tabIndex="0"
class="styled-test-link"
id="test-a11y-role-link"
>
Role
</a>
</div>
<script>
function updateActionMessage(message) {
document.getElementById('test-a11y-click-result').innerText = message
}
// Button Listeners
document.getElementById('test-a11y-semantic-button').addEventListener('click', function () {
updateActionMessage('Click - SEMANTIC Button')
})
document.getElementById('test-a11y-styled-button').addEventListener('click', function () {
updateActionMessage('Click - STYLED Button')
})
document.getElementById('test-a11y-role-button').addEventListener('click', function () {
updateActionMessage('Click - ROLE Button')
})
document.getElementById('test-a11y-role-button').addEventListener('keydown', function (event) {
if (event.key === "Enter" || event.key === ' ') {
updateActionMessage(`Keydown - ${event.key === 'Enter' ? event.key : 'Space' } - ROLE Button`)
}
})
// Link Listeners
document.getElementById('test-a11y-semantic-link').addEventListener('click', function () {
updateActionMessage('Click - SEMANTIC Link')
})
document.getElementById('test-a11y-styled-link').addEventListener('click', function () {
updateActionMessage('Click - STYLED Link')
})
document.getElementById('test-a11y-role-link').addEventListener('click', function () {
updateActionMessage('Click - ROLE Link')
})
document.getElementById('test-a11y-role-link').addEventListener('keydown', function (event) {
if (event.key === "Enter") {
updateActionMessage(`Keydown - Enter - ROLE Link`)
}
})
</script>
</body>
</html>

View File

@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<title>A11y Test Bed - Inputs</title>
<style>
.styled-test-checkbox {
border: 1px solid #767676;
border-radius: 3px;
display: inline-block;
height: 11px;
width: 11px;
}
.svg-icon {
position: relative;
top: -2px;
left: -2px;
color: white;
}
.styled-test-checkbox .svg-icon {
display: none;
}
.styled-test-checkbox[aria-checked="true"] .svg-icon,
.styled-test-checkbox.checked .svg-icon {
display: block;
}
.styled-test-checkbox[aria-checked="true"],
.styled-test-checkbox.checked {
background-color: #0075ff;
}
.styled-test-text {
border: 1px solid #767676;
border-radius: 3px;
display: inline-block;
line-height: 17px;
min-width: 147px;
padding: 1px 2px;
}
</style>
</head>
<body>
<div>
<!-- Semantic Checkbox -->
<label for="test-a11y-semantic-checkbox">Semantic Checkbox</label>
<input
type="checkbox"
id="test-a11y-semantic-checkbox"
/>
<!-- Styled Checkbox -->
<label for="test-a11y-styled-checkbox">Styled Checkbox</label>
<div
id="test-a11y-styled-checkbox"
class="styled-test-checkbox"
>
<svg xmlns="http://www.w3.org/2000/svg" class="svg-icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1"><path d="M896 213.34016q18.3296 0 30.49472 12.16512t12.16512 30.49472q0 18.00192-12.32896 30.33088l-512 512q-12.32896 12.32896-30.33088 12.32896t-30.33088-12.32896l-256-256q-12.32896-12.32896-12.32896-30.33088 0-18.3296 12.16512-30.49472t30.49472-12.16512q18.00192 0 30.33088 12.32896l225.66912 225.9968 481.66912-481.9968q12.32896-12.32896 30.33088-12.32896z"/></svg>
</div>
<!-- Role Checkbox -->
<label id="test-a11y-role-checkbox-label">Role Checkbox</label>
<div
role="checkbox"
tabIndex="0"
id="test-a11y-role-checkbox"
class="styled-test-checkbox"
aria-checked="false"
aria-labelledby="test-a11y-role-checkbox-label"
>
<svg xmlns="http://www.w3.org/2000/svg" class="svg-icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1"><path d="M896 213.34016q18.3296 0 30.49472 12.16512t12.16512 30.49472q0 18.00192-12.32896 30.33088l-512 512q-12.32896 12.32896-30.33088 12.32896t-30.33088-12.32896l-256-256q-12.32896-12.32896-12.32896-30.33088 0-18.3296 12.16512-30.49472t30.49472-12.16512q18.00192 0 30.33088 12.32896l225.66912 225.9968 481.66912-481.9968q12.32896-12.32896 30.33088-12.32896z"/></svg>
</div>
<fieldset>
<legend>Various Text Inputs</legend>
<div>
<label for="test-a11y-semantic-text">Semantic Text</label>
<br />
<input
type="text"
name="test-a11y-semantic-text"
id="test-a11y-semantic-text"
/>
</div>
<div>
<label for="test-a11y-styled-text">Styled Text</label>
<br />
<div
id="test-a11y-styled-text"
contenteditable="true"
class="styled-test-text"
/>
</div>
<div>
<label id="test-a11y-role-text-label">Role Text</label>
<br />
<div
role="textbox"
aria-labelledby="test-a11y-role-text-label"
id="test-a11y-role-text"
contenteditable="true"
class="styled-test-text"
/>
</div>
</fieldset>
</div>
<script>
document.getElementById('test-a11y-styled-checkbox').addEventListener('click', function (event) {
event.currentTarget.classList.toggle('checked')
})
document.getElementById('test-a11y-role-checkbox').addEventListener('click', function (event) {
const currentValue = event.currentTarget.getAttribute('aria-checked')
event.currentTarget.setAttribute('aria-checked', currentValue !== 'true' ? 'true' : 'false')
})
document.getElementById('test-a11y-role-checkbox').addEventListener('keydown', function (event) {
if (event.key === ' ') {
const currentValue = event.currentTarget.getAttribute('aria-checked')
event.currentTarget.setAttribute('aria-checked', currentValue !== 'true' ? 'true' : 'false')
}
})
</script>
</body>
</html>

View File

@@ -0,0 +1,232 @@
<!DOCTYPE html>
<html>
<head>
<title>A11y Test Bed - Range Slider</title>
<style>
.styled-test-slider-container {
width: 129px;
height: 8px;
background-color: #0075ff;
display: inline-block;
margin: 2px;
border-radius: 8px;
background-size: 125px 8px;
}
.styled-test-slider {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #0075ff;
position: relative;
left: calc(50% - 10px);
top: -50%;
z-index: 1;
}
.styled-label {
display: block;
}
</style>
</head>
<body>
<div>
<!-- Semantic Slider -->
<label
for="test-a11y-semantic-slider"
class="styled-label"
>
Semantic Points
</label>
<input
type="range"
min="0"
max="10"
value="5"
id="test-a11y-semantic-slider"
/>
<!-- Styled Slider -->
<div>
<label
id="test-a11y-styled-slider-label"
class="styled-label"
>
Styled Points
</label>
<div
id="test-a11y-styled-slider-container"
class="styled-test-slider-container"
>
<div
id="test-a11y-styled-slider"
class="styled-test-slider"
/>
</div>
</div>
<!-- Role Slider -->
<div>
<label
id="test-a11y-role-slider-label"
class="styled-label"
>
Role Points
</label>
<div
role="slider"
id="test-a11y-role-slider-container"
aria-orientation="horizontal"
tabindex="0"
aria-valuemin="0"
aria-valuenow="5"
aria-valuetext="5 Points"
aria-valuemax="10"
aria-labelledby="test-a11y-role-slider-label"
class="styled-test-slider-container"
>
<div
id="test-a11y-role-slider"
class="styled-test-slider"
/>
</div>
</div>
</div>
<script>
let isStyledMoving = false
let isRoleMoving = false
function getMousePosition(event) {
const parentContainer = event.currentTarget
const containerRect = parentContainer.getBoundingClientRect()
let offset = Math.floor(event.clientX - containerRect.x - 8)
offset = Math.min(Math.max(0, offset), containerRect.width - 16)
const segment = (containerRect.width - 16) / 10
const value = Math.round(offset / segment)
offset = value * segment
return { offset, value }
}
document.addEventListener('pointerup', function (event) {
isStyledMoving = false
isRoleMoving = false
event.preventDefault()
event.stopPropagation()
})
// Styled Listeners
document.getElementById('test-a11y-styled-slider-container').addEventListener('pointermove', function (event) {
if (isStyledMoving) {
const { offset } = getMousePosition(event)
document.getElementById('test-a11y-styled-slider').style.left = `${offset}px`
event.preventDefault()
event.stopPropagation()
}
})
document.getElementById('test-a11y-styled-slider-container').addEventListener('pointerdown', function (event) {
isStyledMoving = true
event.preventDefault()
event.stopPropagation()
})
document.getElementById('test-a11y-styled-slider-container').addEventListener('click', function (event) {
const { offset } = getMousePosition(event)
document.getElementById('test-a11y-styled-slider').style.left = `${offset}px`
event.preventDefault()
event.stopPropagation()
})
// Role Listeners
document.getElementById('test-a11y-role-slider-container').addEventListener('pointermove', function (event) {
if (isRoleMoving) {
const { offset, value } = getMousePosition(event)
event.currentTarget.setAttribute('aria-valuenow', value)
event.currentTarget.setAttribute('aria-valuetext', `${value} Points`)
document.getElementById('test-a11y-role-slider').style.left = `${offset}px`
event.preventDefault()
event.stopPropagation()
}
})
document.getElementById('test-a11y-role-slider-container').addEventListener('pointerdown', function (event) {
isRoleMoving = true
event.preventDefault()
event.stopPropagation()
})
document.getElementById('test-a11y-role-slider-container').addEventListener('click', function (event) {
const { offset, value } = getMousePosition(event)
event.currentTarget.setAttribute('aria-valuenow', value)
event.currentTarget.setAttribute('aria-valuetext', `${value} Points`)
document.getElementById('test-a11y-role-slider').style.left = `${offset}px`
event.currentTarget.focus()
event.preventDefault()
event.stopPropagation()
})
document.getElementById('test-a11y-role-slider-container').addEventListener('keydown', function (event) {
const value = parseInt(event.currentTarget.getAttribute('aria-valuenow'), 10)
let newValue = value
let isUpdating = false
switch (event.key) {
case 'ArrowLeft':
case 'ArrowDown':
newValue -= 1
break
case 'ArrowRight':
case 'ArrowUp':
newValue += 1
break
case 'PageDown':
newValue -= 2
break
case 'PageUp':
newValue += 2
break
case 'Home':
newValue = 0
break
case 'End':
newValue = 10
break
default:
break
}
newValue = Math.max(Math.min(newValue, 10), 0)
if (newValue !== value) {
event.currentTarget.setAttribute('aria-valuenow', newValue)
event.currentTarget.setAttribute('aria-valuetext', `${newValue} Points`)
const parentContainer = event.currentTarget
const containerRect = parentContainer.getBoundingClientRect()
const segment = (containerRect.width - 16) / 10
document.getElementById('test-a11y-role-slider').style.left = `${newValue * segment}px`
event.preventDefault()
event.stopPropagation()
}
})
</script>
</body>
</html>

View File

@@ -148,7 +148,7 @@ export const WIZARD_DEPENDENCY_SVELTE: Cypress.CypressComponentDependency = {
package: 'svelte',
installer: 'svelte',
description: 'Cybernetically enhanced web apps',
minVersion: '^3.0.0',
minVersion: '^3.0.0 || ^4.0.0',
} as const
export const WIZARD_DEPENDENCIES = [

View File

@@ -20,7 +20,7 @@ import type { BrowserInstance } from './browsers/types'
const debug = Debug('cypress:server:open_project')
export class OpenProject {
private projectBase: ProjectBase<any> | null = null
private projectBase: ProjectBase | null = null
relaunchBrowser: (() => Promise<BrowserInstance | null>) = () => {
throw new Error('bad relaunch')
}
@@ -243,7 +243,7 @@ export class OpenProject {
debug(`New url is ${newSpecUrl}`)
this.projectBase.server._socket.changeToUrl(newSpecUrl)
this.projectBase.server.socket.changeToUrl(newSpecUrl)
}
changeUrlToDebug (runNumber: number) {
@@ -259,7 +259,7 @@ export class OpenProject {
debug(`New url is ${newUrl}`)
this.projectBase.server._socket.changeToUrl(newUrl)
this.projectBase.server.socket.changeToUrl(newUrl)
}
/**
@@ -268,7 +268,7 @@ export class OpenProject {
* @returns
*/
updateTelemetryContext (context: string) {
return this.projectBase?.server._socket.updateTelemetryContext(context)
return this.projectBase?.server.socket.updateTelemetryContext(context)
}
// close existing open project if it exists, for example

View File

@@ -13,8 +13,6 @@ import preprocessor from './plugins/preprocessor'
import runEvents from './plugins/run_events'
import Reporter from './reporter'
import * as savedState from './saved_state'
import { ServerCt } from './server-ct'
import { ServerE2E } from './server-e2e'
import { SocketCt } from './socket-ct'
import { SocketE2E } from './socket-e2e'
import { ensureProp } from './util/class-helpers'
@@ -24,11 +22,13 @@ import type { BannersState, FoundBrowser, FoundSpec, OpenProjectLaunchOptions, R
import { DataContext, getCtx } from '@packages/data-context'
import { createHmac } from 'crypto'
import type ProtocolManager from './cloud/protocol'
import { ServerBase } from './server-base'
export interface Cfg extends ReceivedCypressOptions {
projectId?: string
projectRoot: string
proxyServer?: Cypress.RuntimeConfigOptions['proxyUrl']
fileServerFolder?: Cypress.ResolvedConfigOptions['fileServerFolder']
testingType: TestingType
protocolEnabled?: boolean
hideCommandLog?: boolean
@@ -52,15 +52,13 @@ const debug = Debug('cypress:server:project')
type StartWebsocketOptions = Pick<Cfg, 'socketIoCookie' | 'namespace' | 'screenshotsFolder' | 'report' | 'reporter' | 'reporterOptions' | 'projectRoot'>
export type Server = ServerE2E | ServerCt
export class ProjectBase<TServer extends Server> extends EE {
export class ProjectBase extends EE {
// id is sha256 of projectRoot
public id: string
protected ctx: DataContext
protected _cfg?: Cfg
protected _server?: TServer
protected _server?: ServerBase<any>
protected _automation?: Automation
private _protocolManager?: ProtocolManager
private _recordTests?: any = null
@@ -142,12 +140,6 @@ export class ProjectBase<TServer extends Server> extends EE {
return this._server?.remoteStates
}
createServer (testingType: Cypress.TestingType) {
return testingType === 'e2e'
? new ServerE2E() as TServer
: new ServerCt() as TServer
}
async open () {
debug('opening project instance %s', this.projectRoot)
debug('project open options %o', this.options)
@@ -156,7 +148,7 @@ export class ProjectBase<TServer extends Server> extends EE {
process.chdir(this.projectRoot)
this._server = this.createServer(this.testingType)
this._server = new ServerBase()
const [port, warning] = await this._server.open(cfg, {
getCurrentBrowser: () => this.browser,

View File

@@ -1,61 +0,0 @@
import Debug from 'debug'
import { Request, Response, Router } from 'express'
import type { InitializeRoutes } from './routes'
import send from 'send'
import { getPathToDist } from '@packages/resolve-dist'
const debug = Debug('cypress:server:routes-ct')
const serveChunk = (req: Request, res: Response, clientRoute: string) => {
let pathToFile = getPathToDist('runner', req.originalUrl.replace(clientRoute, ''))
return send(req, pathToFile).pipe(res)
}
export const createRoutesCT = ({
config,
nodeProxy,
}: InitializeRoutes) => {
const routesCt = Router()
routesCt.get(`/${config.namespace}/static/*`, (req, res) => {
debug(`proxying to %s/static, originalUrl %s`, config.namespace, req.originalUrl)
const pathToFile = getPathToDist('static', req.params[0])
return send(req, pathToFile)
.pipe(res)
})
// user app code + spec code
// default mounted to /__cypress/src/*
routesCt.get(`${config.devServerPublicPathRoute}*`, (req, res) => {
debug(`proxying to %s, originalUrl %s`, config.devServerPublicPathRoute, req.originalUrl)
// user the node proxy here instead of the network proxy
// to avoid the user accidentally intercepting and modifying
// their own app.js files + spec.js files
nodeProxy.web(req, res, {}, (e) => {
if (e) {
// eslint-disable-next-line
debug('Proxy request error. This is likely the socket hangup issue, we can basically ignore this because the stream will automatically continue once the asset will be available', e)
}
})
})
const clientRoute = config.clientRoute
if (!clientRoute) {
throw Error(`clientRoute is required. Received ${clientRoute}`)
}
// enables runner to make a dynamic import
routesCt.get([
`${clientRoute}ctChunk-*`,
`${clientRoute}vendors~ctChunk-*`,
], (req, res) => {
debug('Serving Cypress front-end chunk by requested URL:', req.url)
serveChunk(req, res, clientRoute)
})
return routesCt
}

View File

@@ -1,113 +0,0 @@
import bodyParser from 'body-parser'
import Debug from 'debug'
import { Router } from 'express'
import path from 'path'
import AppData from './util/app_data'
import CacheBuster from './util/cache_buster'
import specController from './controllers/spec'
import reporter from './controllers/reporter'
import client from './controllers/client'
import files from './controllers/files'
import type { InitializeRoutes } from './routes'
import * as plugins from './plugins'
import { privilegedCommandsManager } from './privileged-commands/privileged-commands-manager'
const debug = Debug('cypress:server:routes-e2e')
export const createRoutesE2E = ({
config,
networkProxy,
onError,
}: InitializeRoutes) => {
const routesE2E = Router()
// routing for the actual specs which are processed automatically
// this could be just a regular .js file or a .coffee file
routesE2E.get(`/${config.namespace}/tests`, (req, res, next) => {
// slice out the cache buster
const test = CacheBuster.strip(req.query.p)
specController.handle(test, req, res, config, next, onError)
})
routesE2E.post(`/${config.namespace}/process-origin-callback`, bodyParser.json(), async (req, res) => {
try {
const { file, fn, projectRoot } = req.body
debug('process origin callback: %s', fn)
const contents = await plugins.execute('_process:cross:origin:callback', { file, fn, projectRoot })
res.json({ contents })
} catch (err) {
const errorMessage = `Processing the origin callback errored:\n\n${err.stack}`
debug(errorMessage)
res.json({
error: errorMessage,
})
}
})
routesE2E.get(`/${config.namespace}/socket.io.js`, (req, res) => {
client.handle(req, res)
})
routesE2E.get(`/${config.namespace}/reporter/*`, (req, res) => {
reporter.handle(req, res)
})
routesE2E.get(`/${config.namespace}/automation/getLocalStorage`, (req, res) => {
res.sendFile(path.join(__dirname, './html/get-local-storage.html'))
})
routesE2E.get(`/${config.namespace}/automation/setLocalStorage`, (req, res) => {
const origin = req.originalUrl.slice(req.originalUrl.indexOf('?') + 1)
networkProxy.http.getRenderedHTMLOrigins()[origin] = true
res.sendFile(path.join(__dirname, './html/set-local-storage.html'))
})
routesE2E.get(`/${config.namespace}/source-maps/:id.map`, (req, res) => {
networkProxy.handleSourceMapRequest(req, res)
})
// special fallback - serve dist'd (bundled/static) files from the project path folder
routesE2E.get(`/${config.namespace}/bundled/*`, (req, res) => {
const file = AppData.getBundledFilePath(config.projectRoot, path.join('src', req.params[0]))
debug(`Serving dist'd bundle at file path: %o`, { path: file, url: req.url })
res.sendFile(file, { etag: false })
})
// TODO: The below route is not technically correct for cypress in cypress tests.
// We should be using 'config.namespace' to provide the namespace instead of hard coding __cypress, however,
// In the runner when we create the spec bridge we have no knowledge of the namespace used by the server so
// we create a spec bridge for the namespace of the server specified in the config, but that server hasn't been created.
// To fix this I think we need to find a way to listen in the cypress in cypress server for routes from the server the
// cypress instance thinks should exist, but that's outside the current scope.
routesE2E.get('/__cypress/spec-bridge-iframes', (req, res) => {
debug('handling cross-origin iframe for domain: %s', req.hostname)
// Chrome plans to make document.domain immutable in Chrome 109, with the default value
// of the Origin-Agent-Cluster header becoming 'true'. We explicitly disable this header
// in the spec-bridge-iframe to allow setting document.domain to the bare domain
// to guarantee the spec bridge can communicate with the injected code.
// @see https://github.com/cypress-io/cypress/issues/25010
res.setHeader('Origin-Agent-Cluster', '?0')
files.handleCrossOriginIframe(req, res, config)
})
routesE2E.post(`/${config.namespace}/add-verified-command`, bodyParser.json(), (req, res) => {
privilegedCommandsManager.addVerifiedCommand(req.body)
res.sendStatus(204)
})
return routesE2E
}

View File

@@ -3,7 +3,6 @@ import Debug from 'debug'
import { ErrorRequestHandler, Request, Router } from 'express'
import send from 'send'
import { getPathToDist } from '@packages/resolve-dist'
import type { NetworkProxy } from '@packages/proxy'
import type { Cfg } from './project-base'
import xhrs from './controllers/xhrs'
@@ -13,6 +12,16 @@ import type { FoundSpec } from '@packages/types'
import { getCtx } from '@packages/data-context'
import { graphQLHTTP } from '@packages/graphql/src/makeGraphQLServer'
import type { RemoteStates } from './remote_states'
import bodyParser from 'body-parser'
import path from 'path'
import AppData from './util/app_data'
import CacheBuster from './util/cache_buster'
import specController from './controllers/spec'
import reporter from './controllers/reporter'
import client from './controllers/client'
import files from './controllers/files'
import * as plugins from './plugins'
import { privilegedCommandsManager } from './privileged-commands/privileged-commands-manager'
const debug = Debug('cypress:server:routes')
@@ -33,10 +42,96 @@ export const createCommonRoutes = ({
getSpec,
remoteStates,
nodeProxy,
onError,
}: InitializeRoutes) => {
const router = Router()
const { clientRoute, namespace } = config
router.get(`/${config.namespace}/tests`, (req, res, next) => {
// slice out the cache buster
const test = CacheBuster.strip(req.query.p)
specController.handle(test, req, res, config, next, onError)
})
router.post(`/${config.namespace}/process-origin-callback`, bodyParser.json(), async (req, res) => {
try {
const { file, fn, projectRoot } = req.body
debug('process origin callback: %s', fn)
const contents = await plugins.execute('_process:cross:origin:callback', { file, fn, projectRoot })
res.json({ contents })
} catch (err) {
const errorMessage = `Processing the origin callback errored:\n\n${err.stack}`
debug(errorMessage)
res.json({
error: errorMessage,
})
}
})
router.get(`/${config.namespace}/socket.io.js`, (req, res) => {
client.handle(req, res)
})
router.get(`/${config.namespace}/reporter/*`, (req, res) => {
reporter.handle(req, res)
})
router.get(`/${config.namespace}/automation/getLocalStorage`, (req, res) => {
res.sendFile(path.join(__dirname, './html/get-local-storage.html'))
})
router.get(`/${config.namespace}/automation/setLocalStorage`, (req, res) => {
const origin = req.originalUrl.slice(req.originalUrl.indexOf('?') + 1)
networkProxy.http.getRenderedHTMLOrigins()[origin] = true
res.sendFile(path.join(__dirname, './html/set-local-storage.html'))
})
router.get(`/${config.namespace}/source-maps/:id.map`, (req, res) => {
networkProxy.handleSourceMapRequest(req, res)
})
// special fallback - serve dist'd (bundled/static) files from the project path folder
router.get(`/${config.namespace}/bundled/*`, (req, res) => {
const file = AppData.getBundledFilePath(config.projectRoot, path.join('src', req.params[0]))
debug(`Serving dist'd bundle at file path: %o`, { path: file, url: req.url })
res.sendFile(file, { etag: false })
})
// TODO: The below route is not technically correct for cypress in cypress tests.
// We should be using 'config.namespace' to provide the namespace instead of hard coding __cypress, however,
// In the runner when we create the spec bridge we have no knowledge of the namespace used by the server so
// we create a spec bridge for the namespace of the server specified in the config, but that server hasn't been created.
// To fix this I think we need to find a way to listen in the cypress in cypress server for routes from the server the
// cypress instance thinks should exist, but that's outside the current scope.
router.get('/__cypress/spec-bridge-iframes', (req, res) => {
debug('handling cross-origin iframe for domain: %s', req.hostname)
// Chrome plans to make document.domain immutable in Chrome 109, with the default value
// of the Origin-Agent-Cluster header becoming 'true'. We explicitly disable this header
// in the spec-bridge-iframe to allow setting document.domain to the bare domain
// to guarantee the spec bridge can communicate with the injected code.
// @see https://github.com/cypress-io/cypress/issues/25010
res.setHeader('Origin-Agent-Cluster', '?0')
files.handleCrossOriginIframe(req, res, config)
})
router.post(`/${config.namespace}/add-verified-command`, bodyParser.json(), (req, res) => {
privilegedCommandsManager.addVerifiedCommand(req.body)
res.sendStatus(204)
})
if (process.env.CYPRESS_INTERNAL_VITE_DEV) {
const proxy = httpProxy.createProxyServer({
target: `http://localhost:${process.env.CYPRESS_INTERNAL_VITE_APP_PORT}/`,
@@ -102,6 +197,24 @@ export const createCommonRoutes = ({
return send(req, pathToFile).pipe(res)
})
// user app code + spec code
// default mounted to /__cypress/src/*
// TODO: Remove this - only needed for Cy in Cy testing for unknown reasons.
if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
router.get(`${config.devServerPublicPathRoute}*`, (req, res) => {
debug(`proxying to %s, originalUrl %s`, config.devServerPublicPathRoute, req.originalUrl)
// user the node proxy here instead of the network proxy
// to avoid the user accidentally intercepting and modifying
// their own app.js files + spec.js files
nodeProxy.web(req, res, {}, (e) => {
if (e) {
// eslint-disable-next-line
debug('Proxy request error. This is likely the socket hangup issue, we can basically ignore this because the stream will automatically continue once the asset will be available', e)
}
})
})
}
router.all('*', (req, res) => {
networkProxy.handleHttpRequest(req, res)
})

View File

@@ -4,6 +4,7 @@ import compression from 'compression'
import Debug from 'debug'
import EventEmitter from 'events'
import evilDns from 'evil-dns'
import * as ensureUrl from './util/ensure-url'
import express, { Express } from 'express'
import http from 'http'
import httpProxy from 'http-proxy'
@@ -11,9 +12,9 @@ import _ from 'lodash'
import type { AddressInfo } from 'net'
import url from 'url'
import la from 'lazy-ass'
import type httpsProxy from '@packages/https-proxy'
import { netStubbingState, NetStubbingState } from '@packages/net-stubbing'
import { agent, clientCertificates, cors, httpUtils, uri } from '@packages/network'
import httpsProxy from '@packages/https-proxy'
import { getRoutesForRequest, netStubbingState, NetStubbingState } from '@packages/net-stubbing'
import { agent, clientCertificates, cors, httpUtils, uri, concatStream } from '@packages/network'
import { NetworkProxy, BrowserPreRequest } from '@packages/proxy'
import type { SocketCt } from './socket-ct'
import * as errors from './errors'
@@ -29,14 +30,41 @@ import type { Browser } from '@packages/server/lib/browsers/types'
import { InitializeRoutes, createCommonRoutes } from './routes'
import { createRoutesE2E } from './routes-e2e'
import { createRoutesCT } from './routes-ct'
import type { FoundSpec, ProtocolManagerShape } from '@packages/types'
import type { FoundSpec, ProtocolManagerShape, TestingType } from '@packages/types'
import type { Server as WebSocketServer } from 'ws'
import { RemoteStates } from './remote_states'
import { cookieJar, SerializableAutomationCookie } from './util/cookies'
import { resourceTypeAndCredentialManager, ResourceTypeAndCredentialManager } from './util/resourceTypeAndCredentialManager'
import fileServer from './file_server'
import appData from './util/app_data'
import { graphqlWS } from '@packages/graphql/src/makeGraphQLServer'
import statusCode from './util/status_code'
import headersUtil from './util/headers'
import stream from 'stream'
import isHtml from 'is-html'
const debug = Debug('cypress:server:server-base')
const fullyQualifiedRe = /^https?:\/\//
const htmlContentTypesRe = /^(text\/html|application\/xhtml)/i
const isResponseHtml = function (contentType, responseBuffer) {
if (contentType) {
// want to match anything starting with 'text/html'
// including 'text/html;charset=utf-8' and 'Text/HTML'
// https://github.com/cypress-io/cypress/issues/8506
return htmlContentTypesRe.test(contentType)
}
const body = _.invoke(responseBuffer, 'toString')
if (body) {
return isHtml(body)
}
return false
}
const _isNonProxiedRequest = (req) => {
// proxied HTTP requests have a URL like: "http://example.com/foo"
// non-proxied HTTP requests have a URL like: "/foo"
@@ -112,7 +140,7 @@ export interface OpenServerOptions {
protocolManager?: ProtocolManagerShape
}
export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
export class ServerBase<TSocket extends SocketE2E | SocketCt> {
private _middleware
private _protocolManager?: ProtocolManagerShape
protected request: Request
@@ -131,6 +159,9 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
protected _eventBus: EventEmitter
protected _remoteStates: RemoteStates
private getCurrentBrowser: undefined | (() => Browser)
private skipDomainInjectionForDomains: string[] | null = null
private _urlResolver: Bluebird<Record<string, any>> | null = null
private testingType?: TestingType
constructor () {
this.isListening = false
@@ -201,11 +232,90 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
this.socket.localBus.on('request:sent:with:credentials', this.resourceTypeAndCredentialManager.set)
}
abstract createServer (
createServer (
app: Express,
config: Cfg,
onWarning: unknown,
): Bluebird<[number, WarningErr?]>
): Bluebird<[number, WarningErr?]> {
return new Bluebird((resolve, reject) => {
const { port, fileServerFolder, socketIoRoute, baseUrl, experimentalSkipDomainInjection } = config
this._server = this._createHttpServer(app)
this.skipDomainInjectionForDomains = experimentalSkipDomainInjection
const onError = (err) => {
// if the server bombs before starting
// and the err no is EADDRINUSE
// then we know to display the custom err message
if (err.code === 'EADDRINUSE') {
return reject(this.portInUseErr(port))
}
}
debug('createServer connecting to server')
this.server.on('connect', this.onConnect.bind(this))
this.server.on('upgrade', (req, socket, head) => this.onUpgrade(req, socket, head, socketIoRoute))
this.server.once('error', onError)
this._graphqlWS = graphqlWS(this.server, `${socketIoRoute}-graphql`)
return this._listen(port, (err) => {
// if the server bombs before starting
// and the err no is EADDRINUSE
// then we know to display the custom err message
if (err.code === 'EADDRINUSE') {
return reject(this.portInUseErr(port))
}
})
.then((port) => {
return Bluebird.all([
httpsProxy.create(appData.path('proxy'), port, {
onRequest: this.callListeners.bind(this),
onUpgrade: this.onSniUpgrade.bind(this),
}),
fileServer.create(fileServerFolder),
])
.spread((httpsProxy, fileServer) => {
this._httpsProxy = httpsProxy
this._fileServer = fileServer
// if we have a baseUrl let's go ahead
// and make sure the server is connectable!
if (baseUrl) {
this._baseUrl = baseUrl
if (config.isTextTerminal) {
return this._retryBaseUrlCheck(baseUrl, onWarning)
.return(null)
.catch((e) => {
debug(e)
return reject(errors.get('CANNOT_CONNECT_BASE_URL'))
})
}
return ensureUrl.isListening(baseUrl)
.return(null)
.catch((err) => {
debug('ensuring baseUrl (%s) errored: %o', baseUrl, err)
return errors.get('CANNOT_CONNECT_BASE_URL_WARNING', baseUrl)
})
}
}).then((warning) => {
// once we open set the domain to root by default
// which prevents a situation where navigating
// to http sites redirects to /__/ cypress
this._remoteStates.set(baseUrl != null ? baseUrl : '<root>')
return resolve([port, warning])
})
})
})
}
open (config: Cfg, {
getSpec,
@@ -219,11 +329,12 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
protocolManager,
}: OpenServerOptions) {
debug('server open')
this.testingType = testingType
la(_.isPlainObject(config), 'expected plain config object', config)
if (!config.baseUrl && testingType === 'component') {
throw new Error('ServerCt#open called without config.baseUrl.')
throw new Error('Server#open called without config.baseUrl.')
}
const app = this.createExpressApp(config)
@@ -263,11 +374,6 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
this.setupCrossOriginRequestHandling()
const runnerSpecificRouter = testingType === 'e2e'
? createRoutesE2E(routeOptions)
: createRoutesCT(routeOptions)
app.use(runnerSpecificRouter)
app.use(createCommonRoutes(routeOptions))
return this.createServer(app, config, onWarning)
@@ -355,6 +461,9 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
}
startWebsockets (automation, config, options: Record<string, unknown> = {}) {
// e2e only?
options.onResolveUrl = this._onResolveUrl.bind(this)
options.onRequest = this._onRequest.bind(this)
options.netStubbingState = this.netStubbingState
options.getRenderedHTMLOrigins = this._networkProxy?.http.getRenderedHTMLOrigins
@@ -588,4 +697,293 @@ export abstract class ServerBase<TSocket extends SocketE2E | SocketCt> {
return this.httpsProxy.connect(req, socket, head)
}
_retryBaseUrlCheck (baseUrl, onWarning) {
return ensureUrl.retryIsListening(baseUrl, {
retryIntervals: [3000, 3000, 4000],
onRetry ({ attempt, delay, remaining }) {
const warning = errors.get('CANNOT_CONNECT_BASE_URL_RETRYING', {
remaining,
attempt,
delay,
baseUrl,
})
return onWarning(warning)
},
})
}
_onResolveUrl (urlStr, headers, automationRequest, options: Record<string, any> = { headers: {} }) {
debug('resolving visit %o', {
url: urlStr,
headers,
options,
})
// always clear buffers - reduces the possibility of a random HTTP request
// accidentally retrieving buffered content at the wrong time
this._networkProxy?.reset()
const startTime = Date.now()
// if we have an existing url resolver
// in flight then cancel it
if (this._urlResolver) {
this._urlResolver.cancel()
}
const request = this.request
let handlingLocalFile = false
const previousRemoteState = this._remoteStates.current()
const previousRemoteStateIsPrimary = this._remoteStates.isPrimarySuperDomainOrigin(previousRemoteState.origin)
const primaryRemoteState = this._remoteStates.getPrimary()
// nuke any hashes from our url since
// those those are client only and do
// not apply to http requests
urlStr = url.parse(urlStr)
urlStr.hash = null
urlStr = urlStr.format()
const originalUrl = urlStr
let reqStream = null
let currentPromisePhase = null
const runPhase = (fn) => {
return currentPromisePhase = fn()
}
const matchesNetStubbingRoute = (requestOptions) => {
const proxiedReq = {
proxiedUrl: requestOptions.url,
resourceType: 'document',
..._.pick(requestOptions, ['headers', 'method']),
// TODO: add `body` here once bodies can be statically matched
}
// @ts-ignore
const iterator = getRoutesForRequest(this.netStubbingState?.routes, proxiedReq)
// If the iterator is exhausted (done) on the first try, then 0 matches were found
const zeroMatches = iterator.next().done
return !zeroMatches
}
let p
return this._urlResolver = (p = new Bluebird<Record<string, any>>((resolve, reject, onCancel) => {
let urlFile
onCancel?.(() => {
p.currentPromisePhase = currentPromisePhase
p.reqStream = reqStream
_.invoke(reqStream, 'abort')
return _.invoke(currentPromisePhase, 'cancel')
})
const redirects: any[] = []
let newUrl: string | null = null
if (!fullyQualifiedRe.test(urlStr)) {
handlingLocalFile = true
options.headers['x-cypress-authorization'] = this._fileServer?.token
const state = this._remoteStates.set(urlStr, options)
// TODO: Update url.resolve signature to not use deprecated methods
urlFile = url.resolve(state.fileServer as string, urlStr)
urlStr = url.resolve(state.origin as string, urlStr)
}
const onReqError = (err) => {
// only restore the previous state
// if our promise is still pending
if (p.isPending()) {
restorePreviousRemoteState(previousRemoteState, previousRemoteStateIsPrimary)
}
return reject(err)
}
const onReqStreamReady = (str) => {
reqStream = str
return str
.on('error', onReqError)
.on('response', (incomingRes) => {
debug(
'resolve:url headers received, buffering response %o',
_.pick(incomingRes, 'headers', 'statusCode'),
)
if (newUrl == null) {
newUrl = urlStr
}
return runPhase(() => {
// get the cookies that would be sent with this request so they can be rehydrated
return automationRequest('get:cookies', {
domain: cors.getSuperDomain(newUrl),
})
.then((cookies) => {
const statusIs2xxOrAllowedFailure = () => {
// is our status code in the 2xx range, or have we disabled failing
// on status code?
return statusCode.isOk(incomingRes.statusCode) || options.failOnStatusCode === false
}
const isOk = statusIs2xxOrAllowedFailure()
const contentType = headersUtil.getContentType(incomingRes)
const details: Record<string, unknown> = {
isOkStatusCode: isOk,
contentType,
url: newUrl,
status: incomingRes.statusCode,
cookies,
statusText: statusCode.getText(incomingRes.statusCode),
redirects,
originalUrl,
}
// does this response have this cypress header?
const fp = incomingRes.headers['x-cypress-file-path']
if (fp) {
// if so we know this is a local file request
details.filePath = fp
}
debug('setting details resolving url %o', details)
const concatStr = concatStream((responseBuffer) => {
// buffer the entire response before resolving.
// this allows us to detect & reject ETIMEDOUT errors
// where the headers have been sent but the
// connection hangs before receiving a body.
// if there is not a content-type, try to determine
// if the response content is HTML-like
// https://github.com/cypress-io/cypress/issues/1727
details.isHtml = isResponseHtml(contentType, responseBuffer)
debug('resolve:url response ended, setting buffer %o', { newUrl, details })
details.totalTime = Date.now() - startTime
// buffer the response and set the remote state if this is a successful html response
// TODO: think about moving this logic back into the frontend so that the driver can be in control
// of when to buffer and set the remote state
if (isOk && details.isHtml) {
const urlDoesNotMatchPolicyBasedOnDomain = options.hasAlreadyVisitedUrl
&& !cors.urlMatchesPolicyBasedOnDomain(primaryRemoteState.origin, newUrl || '', { skipDomainInjectionForDomains: this.skipDomainInjectionForDomains })
|| options.isFromSpecBridge
if (!handlingLocalFile) {
this._remoteStates.set(newUrl as string, options, !urlDoesNotMatchPolicyBasedOnDomain)
}
const responseBufferStream = new stream.PassThrough({
highWaterMark: Number.MAX_SAFE_INTEGER,
})
responseBufferStream.end(responseBuffer)
this._networkProxy?.setHttpBuffer({
url: newUrl,
stream: responseBufferStream,
details,
originalUrl,
response: incomingRes,
urlDoesNotMatchPolicyBasedOnDomain,
})
} else {
// TODO: move this logic to the driver too for
// the same reasons listed above
restorePreviousRemoteState(previousRemoteState, previousRemoteStateIsPrimary)
}
details.isPrimarySuperDomainOrigin = this._remoteStates.isPrimarySuperDomainOrigin(newUrl!)
return resolve(details)
})
return str.pipe(concatStr)
}).catch(onReqError)
})
})
}
const restorePreviousRemoteState = (previousRemoteState: Cypress.RemoteState, previousRemoteStateIsPrimary: boolean) => {
this._remoteStates.set(previousRemoteState, {}, previousRemoteStateIsPrimary)
}
// if they're POSTing an object, querystringify their POST body
if ((options.method === 'POST') && _.isObject(options.body)) {
options.form = options.body
delete options.body
}
_.assign(options, {
// turn off gzip since we need to eventually
// rewrite these contents
gzip: false,
url: urlFile != null ? urlFile : urlStr,
headers: _.assign({
accept: 'text/html,*/*',
}, options.headers),
onBeforeReqInit: runPhase,
followRedirect (incomingRes) {
const status = incomingRes.statusCode
const next = incomingRes.headers.location
const curr = newUrl != null ? newUrl : urlStr
newUrl = url.resolve(curr, next)
redirects.push([status, newUrl].join(': '))
return true
},
})
if (matchesNetStubbingRoute(options)) {
// TODO: this is being used to force cy.visits to be interceptable by network stubbing
// however, network errors will be obsfucated by the proxying so this is not an ideal solution
_.merge(options, {
proxy: `http://127.0.0.1:${this._port()}`,
agent: null,
headers: {
'x-cypress-resolving-url': '1',
},
})
}
debug('sending request with options %o', options)
return runPhase(() => {
// @ts-ignore
return request.sendStream(headers, automationRequest, options)
.then((createReqStream) => {
const stream = createReqStream()
return onReqStreamReady(stream)
}).catch(onReqError)
})
}))
}
destroyAut () {
if (this.testingType === 'component' && 'destroyAut' in this.socket) {
return this.socket.destroyAut()
}
return
}
}

View File

@@ -1,55 +0,0 @@
import Bluebird from 'bluebird'
import httpsProxy from '@packages/https-proxy'
import { OpenServerOptions, ServerBase } from '@packages/server/lib/server-base'
import appData from '@packages/server/lib/util/app_data'
import type { SocketCt } from './socket-ct'
import type { Cfg } from '@packages/server/lib/project-base'
import { graphqlWS } from '@packages/graphql/src/makeGraphQLServer'
type WarningErr = Record<string, any>
export class ServerCt extends ServerBase<SocketCt> {
open (config: Cfg, options: OpenServerOptions) {
return super.open(config, { ...options, testingType: 'component' })
}
createServer (app, config, onWarning): Bluebird<[number, WarningErr?]> {
return new Bluebird((resolve, reject) => {
const { port, baseUrl, socketIoRoute } = config
this._server = this._createHttpServer(app)
this.server.on('connect', this.onConnect.bind(this))
this.server.on('upgrade', (req, socket, head) => this.onUpgrade(req, socket, head, socketIoRoute))
this._graphqlWS = graphqlWS(this.server, `${socketIoRoute}-graphql`)
return this._listen(port, (err) => {
if (err.code === 'EADDRINUSE') {
reject(`Port ${port} is already in use`)
}
reject(err)
})
.then((port) => {
httpsProxy.create(appData.path('proxy'), port, {
onRequest: this.callListeners.bind(this),
onUpgrade: this.onSniUpgrade.bind(this),
})
.then((httpsProxy) => {
this._httpsProxy = httpsProxy
// once we open set the domain to root by default
// which prevents a situation where navigating
// to http sites redirects to /__/ cypress
this._remoteStates.set(baseUrl)
return resolve([port])
})
})
})
}
destroyAut () {
return this.socket.destroyAut()
}
}

View File

@@ -1,430 +0,0 @@
import Bluebird from 'bluebird'
import Debug from 'debug'
import isHtml from 'is-html'
import _ from 'lodash'
import stream from 'stream'
import url from 'url'
import httpsProxy from '@packages/https-proxy'
import { getRoutesForRequest } from '@packages/net-stubbing'
import { concatStream, cors } from '@packages/network'
import { graphqlWS } from '@packages/graphql/src/makeGraphQLServer'
import * as errors from './errors'
import fileServer from './file_server'
import { OpenServerOptions, ServerBase } from './server-base'
import type { SocketE2E } from './socket-e2e'
import appData from './util/app_data'
import * as ensureUrl from './util/ensure-url'
import headersUtil from './util/headers'
import statusCode from './util/status_code'
import type { Cfg } from './project-base'
type WarningErr = Record<string, any>
const fullyQualifiedRe = /^https?:\/\//
const htmlContentTypesRe = /^(text\/html|application\/xhtml)/i
const debug = Debug('cypress:server:server-e2e')
const isResponseHtml = function (contentType, responseBuffer) {
if (contentType) {
// want to match anything starting with 'text/html'
// including 'text/html;charset=utf-8' and 'Text/HTML'
// https://github.com/cypress-io/cypress/issues/8506
return htmlContentTypesRe.test(contentType)
}
const body = _.invoke(responseBuffer, 'toString')
if (body) {
return isHtml(body)
}
return false
}
export class ServerE2E extends ServerBase<SocketE2E> {
private _urlResolver: Bluebird<Record<string, any>> | null
// the initialization of this variable is only precautionary as the actual config value is applied when the server is created
private skipDomainInjectionForDomains: string[] | null = null
constructor () {
super()
this._urlResolver = null
}
open (config: Cfg, options: OpenServerOptions) {
return super.open(config, { ...options, testingType: 'e2e' })
}
createServer (app, config, onWarning): Bluebird<[number, WarningErr?]> {
return new Bluebird((resolve, reject) => {
const { port, fileServerFolder, socketIoRoute, baseUrl, experimentalSkipDomainInjection } = config
this._server = this._createHttpServer(app)
this.skipDomainInjectionForDomains = experimentalSkipDomainInjection
const onError = (err) => {
// if the server bombs before starting
// and the err no is EADDRINUSE
// then we know to display the custom err message
if (err.code === 'EADDRINUSE') {
return reject(this.portInUseErr(port))
}
}
debug('createServer connecting to server')
this.server.on('connect', this.onConnect.bind(this))
this.server.on('upgrade', (req, socket, head) => this.onUpgrade(req, socket, head, socketIoRoute))
this.server.once('error', onError)
this._graphqlWS = graphqlWS(this.server, `${socketIoRoute}-graphql`)
return this._listen(port, (err) => {
// if the server bombs before starting
// and the err no is EADDRINUSE
// then we know to display the custom err message
if (err.code === 'EADDRINUSE') {
return reject(this.portInUseErr(port))
}
})
.then((port) => {
return Bluebird.all([
httpsProxy.create(appData.path('proxy'), port, {
onRequest: this.callListeners.bind(this),
onUpgrade: this.onSniUpgrade.bind(this),
}),
fileServer.create(fileServerFolder),
])
.spread((httpsProxy, fileServer) => {
this._httpsProxy = httpsProxy
this._fileServer = fileServer
// if we have a baseUrl let's go ahead
// and make sure the server is connectable!
if (baseUrl) {
this._baseUrl = baseUrl
if (config.isTextTerminal) {
return this._retryBaseUrlCheck(baseUrl, onWarning)
.return(null)
.catch((e) => {
debug(e)
return reject(errors.get('CANNOT_CONNECT_BASE_URL'))
})
}
return ensureUrl.isListening(baseUrl)
.return(null)
.catch((err) => {
debug('ensuring baseUrl (%s) errored: %o', baseUrl, err)
return errors.get('CANNOT_CONNECT_BASE_URL_WARNING', baseUrl)
})
}
}).then((warning) => {
// once we open set the domain to root by default
// which prevents a situation where navigating
// to http sites redirects to /__/ cypress
this._remoteStates.set(baseUrl != null ? baseUrl : '<root>')
return resolve([port, warning])
})
})
})
}
startWebsockets (automation, config, options: Record<string, unknown> = {}) {
options.onResolveUrl = this._onResolveUrl.bind(this)
return super.startWebsockets(automation, config, options)
}
_onResolveUrl (urlStr, headers, automationRequest, options: Record<string, any> = { headers: {} }) {
let p
debug('resolving visit %o', {
url: urlStr,
headers,
options,
})
// always clear buffers - reduces the possibility of a random HTTP request
// accidentally retrieving buffered content at the wrong time
this._networkProxy?.reset()
const startTime = Date.now()
// if we have an existing url resolver
// in flight then cancel it
if (this._urlResolver) {
this._urlResolver.cancel()
}
const request = this.request
let handlingLocalFile = false
const previousRemoteState = this._remoteStates.current()
const previousRemoteStateIsPrimary = this._remoteStates.isPrimarySuperDomainOrigin(previousRemoteState.origin)
const primaryRemoteState = this._remoteStates.getPrimary()
// nuke any hashes from our url since
// those those are client only and do
// not apply to http requests
urlStr = url.parse(urlStr)
urlStr.hash = null
urlStr = urlStr.format()
const originalUrl = urlStr
let reqStream = null
let currentPromisePhase = null
const runPhase = (fn) => {
return currentPromisePhase = fn()
}
const matchesNetStubbingRoute = (requestOptions) => {
const proxiedReq = {
proxiedUrl: requestOptions.url,
resourceType: 'document',
..._.pick(requestOptions, ['headers', 'method']),
// TODO: add `body` here once bodies can be statically matched
}
// @ts-ignore
const iterator = getRoutesForRequest(this.netStubbingState?.routes, proxiedReq)
// If the iterator is exhausted (done) on the first try, then 0 matches were found
const zeroMatches = iterator.next().done
return !zeroMatches
}
return this._urlResolver = (p = new Bluebird<Record<string, any>>((resolve, reject, onCancel) => {
let urlFile
onCancel?.(() => {
p.currentPromisePhase = currentPromisePhase
p.reqStream = reqStream
_.invoke(reqStream, 'abort')
return _.invoke(currentPromisePhase, 'cancel')
})
const redirects: any[] = []
let newUrl: string | null = null
if (!fullyQualifiedRe.test(urlStr)) {
handlingLocalFile = true
options.headers['x-cypress-authorization'] = this._fileServer?.token
const state = this._remoteStates.set(urlStr, options)
// TODO: Update url.resolve signature to not use deprecated methods
urlFile = url.resolve(state.fileServer as string, urlStr)
urlStr = url.resolve(state.origin as string, urlStr)
}
const onReqError = (err) => {
// only restore the previous state
// if our promise is still pending
if (p.isPending()) {
restorePreviousRemoteState(previousRemoteState, previousRemoteStateIsPrimary)
}
return reject(err)
}
const onReqStreamReady = (str) => {
reqStream = str
return str
.on('error', onReqError)
.on('response', (incomingRes) => {
debug(
'resolve:url headers received, buffering response %o',
_.pick(incomingRes, 'headers', 'statusCode'),
)
if (newUrl == null) {
newUrl = urlStr
}
return runPhase(() => {
// get the cookies that would be sent with this request so they can be rehydrated
return automationRequest('get:cookies', {
domain: cors.getSuperDomain(newUrl),
})
.then((cookies) => {
const statusIs2xxOrAllowedFailure = () => {
// is our status code in the 2xx range, or have we disabled failing
// on status code?
return statusCode.isOk(incomingRes.statusCode) || options.failOnStatusCode === false
}
const isOk = statusIs2xxOrAllowedFailure()
const contentType = headersUtil.getContentType(incomingRes)
const details: Record<string, unknown> = {
isOkStatusCode: isOk,
contentType,
url: newUrl,
status: incomingRes.statusCode,
cookies,
statusText: statusCode.getText(incomingRes.statusCode),
redirects,
originalUrl,
}
// does this response have this cypress header?
const fp = incomingRes.headers['x-cypress-file-path']
if (fp) {
// if so we know this is a local file request
details.filePath = fp
}
debug('setting details resolving url %o', details)
const concatStr = concatStream((responseBuffer) => {
// buffer the entire response before resolving.
// this allows us to detect & reject ETIMEDOUT errors
// where the headers have been sent but the
// connection hangs before receiving a body.
// if there is not a content-type, try to determine
// if the response content is HTML-like
// https://github.com/cypress-io/cypress/issues/1727
details.isHtml = isResponseHtml(contentType, responseBuffer)
debug('resolve:url response ended, setting buffer %o', { newUrl, details })
details.totalTime = Date.now() - startTime
// buffer the response and set the remote state if this is a successful html response
// TODO: think about moving this logic back into the frontend so that the driver can be in control
// of when to buffer and set the remote state
if (isOk && details.isHtml) {
const urlDoesNotMatchPolicyBasedOnDomain = options.hasAlreadyVisitedUrl
&& !cors.urlMatchesPolicyBasedOnDomain(primaryRemoteState.origin, newUrl || '', { skipDomainInjectionForDomains: this.skipDomainInjectionForDomains })
|| options.isFromSpecBridge
if (!handlingLocalFile) {
this._remoteStates.set(newUrl as string, options, !urlDoesNotMatchPolicyBasedOnDomain)
}
const responseBufferStream = new stream.PassThrough({
highWaterMark: Number.MAX_SAFE_INTEGER,
})
responseBufferStream.end(responseBuffer)
this._networkProxy?.setHttpBuffer({
url: newUrl,
stream: responseBufferStream,
details,
originalUrl,
response: incomingRes,
urlDoesNotMatchPolicyBasedOnDomain,
})
} else {
// TODO: move this logic to the driver too for
// the same reasons listed above
restorePreviousRemoteState(previousRemoteState, previousRemoteStateIsPrimary)
}
details.isPrimarySuperDomainOrigin = this._remoteStates.isPrimarySuperDomainOrigin(newUrl!)
return resolve(details)
})
return str.pipe(concatStr)
}).catch(onReqError)
})
})
}
const restorePreviousRemoteState = (previousRemoteState: Cypress.RemoteState, previousRemoteStateIsPrimary: boolean) => {
this._remoteStates.set(previousRemoteState, {}, previousRemoteStateIsPrimary)
}
// if they're POSTing an object, querystringify their POST body
if ((options.method === 'POST') && _.isObject(options.body)) {
options.form = options.body
delete options.body
}
_.assign(options, {
// turn off gzip since we need to eventually
// rewrite these contents
gzip: false,
url: urlFile != null ? urlFile : urlStr,
headers: _.assign({
accept: 'text/html,*/*',
}, options.headers),
onBeforeReqInit: runPhase,
followRedirect (incomingRes) {
const status = incomingRes.statusCode
const next = incomingRes.headers.location
const curr = newUrl != null ? newUrl : urlStr
newUrl = url.resolve(curr, next)
redirects.push([status, newUrl].join(': '))
return true
},
})
if (matchesNetStubbingRoute(options)) {
// TODO: this is being used to force cy.visits to be interceptable by network stubbing
// however, network errors will be obsfucated by the proxying so this is not an ideal solution
_.merge(options, {
proxy: `http://127.0.0.1:${this._port()}`,
agent: null,
headers: {
'x-cypress-resolving-url': '1',
},
})
}
debug('sending request with options %o', options)
return runPhase(() => {
// @ts-ignore
return request.sendStream(headers, automationRequest, options)
.then((createReqStream) => {
const stream = createReqStream()
return onReqStreamReady(stream)
}).catch(onReqError)
})
}))
}
onTestFileChange (filePath) {
return this.socket.onTestFileChange(filePath)
}
_retryBaseUrlCheck (baseUrl, onWarning) {
return ensureUrl.retryIsListening(baseUrl, {
retryIntervals: [3000, 3000, 4000],
onRetry ({ attempt, delay, remaining }) {
const warning = errors.get('CANNOT_CONNECT_BASE_URL_RETRYING', {
remaining,
attempt,
delay,
baseUrl,
})
return onWarning(warning)
},
})
}
}

View File

@@ -31,7 +31,7 @@ const cache = require(`../../lib/cache`)
const errors = require(`../../lib/errors`)
const cypress = require(`../../lib/cypress`)
const ProjectBase = require(`../../lib/project-base`).ProjectBase
const { ServerE2E } = require(`../../lib/server-e2e`)
const { ServerBase } = require(`../../lib/server-base`)
const Reporter = require(`../../lib/reporter`)
const browsers = require(`../../lib/browsers`)
const videoCapture = require(`../../lib/video_capture`)
@@ -176,7 +176,7 @@ describe('lib/cypress', () => {
sinon.stub(extension, 'setHostAndPath').resolves()
sinon.stub(detect, 'detect').resolves([...TYPICAL_BROWSERS])
sinon.stub(process, 'exit')
sinon.stub(ServerE2E.prototype, 'reset')
sinon.stub(ServerBase.prototype, 'reset')
sinon.stub(errors, 'warning')
.callThrough()
.withArgs('INVOKED_BINARY_OUTSIDE_NPM_MODULE')
@@ -1105,7 +1105,7 @@ describe('lib/cypress', () => {
it('can change the default port to 5544', function () {
const listen = sinon.spy(http.Server.prototype, 'listen')
const open = sinon.spy(ServerE2E.prototype, 'open')
const open = sinon.spy(ServerBase.prototype, 'open')
return cypress.start([`--run-project=${this.todosPath}`, '--port=5544'])
.then(() => {
@@ -1779,7 +1779,7 @@ describe('lib/cypress', () => {
sinon.stub(electron.app, 'on').withArgs('ready').yieldsAsync()
sinon.stub(Windows, 'open').resolves(this.win)
sinon.stub(ServerE2E.prototype, 'startWebsockets')
sinon.stub(ServerBase.prototype, 'startWebsockets')
sinon.stub(electron.ipcMain, 'on')
})
@@ -1800,7 +1800,7 @@ describe('lib/cypress', () => {
// TODO: fix failing test https://github.com/cypress-io/cypress/issues/23149
it.skip('passes filtered options to Project#open and sets cli config', async function () {
const open = sinon.stub(ServerE2E.prototype, 'open').resolves([])
const open = sinon.stub(ServerBase.prototype, 'open').resolves([])
sinon.stub(interactiveMode, 'ready')

View File

@@ -19,7 +19,7 @@ const SseStream = require('ssestream')
const EventSource = require('eventsource')
const { setupFullConfigWithDefaults } = require('@packages/config')
const config = require(`../../lib/config`)
const { ServerE2E } = require(`../../lib/server-e2e`)
const { ServerBase } = require(`../../lib/server-base`)
const pluginsModule = require(`../../lib/plugins`)
const preprocessor = require(`../../lib/plugins/preprocessor`)
const resolve = require(`../../lib/util/resolve`)
@@ -87,7 +87,7 @@ describe('Routes', () => {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
sinon.stub(CacheBuster, 'get').returns('-123')
sinon.stub(ServerE2E.prototype, 'reset')
sinon.stub(ServerBase.prototype, 'reset')
sinon.stub(pluginsModule, 'has').returns(false)
nock.enableNetConnect()
@@ -161,7 +161,7 @@ describe('Routes', () => {
httpsServer.start(8443),
// and open our cypress server
(this.server = new ServerE2E()),
(this.server = new ServerBase()),
this.server.open(cfg, {
SocketCtor: SocketE2E,

View File

@@ -8,7 +8,7 @@ const evilDns = require('evil-dns')
const { setupFullConfigWithDefaults } = require('@packages/config')
const httpsServer = require(`@packages/https-proxy/test/helpers/https_server`)
const config = require(`../../lib/config`)
const { ServerE2E } = require(`../../lib/server-e2e`)
const { ServerBase } = require(`../../lib/server-base`)
const { SocketE2E } = require(`../../lib/socket-e2e`)
const Fixtures = require('@tooling/system-tests')
const { createRoutes } = require(`../../lib/routes`)
@@ -26,7 +26,7 @@ describe('Server', () => {
require('mocha-banner').register()
beforeEach(() => {
return sinon.stub(ServerE2E.prototype, 'reset')
return sinon.stub(ServerBase.prototype, 'reset')
})
context('resolving url', () => {
@@ -83,7 +83,7 @@ describe('Server', () => {
httpsServer.start(8443),
// and open our cypress server
(this.server = new ServerE2E()),
(this.server = new ServerBase()),
this.server.open(cfg, {
SocketCtor: SocketE2E,

View File

@@ -8,7 +8,7 @@ const Promise = require('bluebird')
const socketIo = require(`@packages/socket/lib/browser`)
const httpsServer = require(`@packages/https-proxy/test/helpers/https_server`)
const config = require(`../../lib/config`)
const { ServerE2E } = require(`../../lib/server-e2e`)
const { ServerBase } = require(`../../lib/server-base`)
const { SocketE2E } = require(`../../lib/socket-e2e`)
const { Automation } = require(`../../lib/automation`)
const Fixtures = require('@tooling/system-tests')
@@ -38,7 +38,7 @@ describe('Web Sockets', () => {
this.cfg = cfg
this.ws = new ws.Server({ port: wsPort })
this.server = new ServerE2E()
this.server = new ServerBase()
return this.server.open(this.cfg, {
SocketCtor: SocketE2E,

View File

@@ -22,7 +22,7 @@ process.env.CYPRESS_INTERNAL_ENV = 'development'
const CA = require('@packages/https-proxy').CA
const { setupFullConfigWithDefaults } = require('@packages/config')
const { ServerE2E } = require('../../lib/server-e2e')
const { ServerBase } = require('../../lib/server-base')
const { SocketE2E } = require('../../lib/socket-e2e')
const { _getArgs } = require('../../lib/browsers/chrome')
@@ -361,7 +361,7 @@ describe('Proxy Performance', function () {
// turn off morgan
config.morgan = false
cyServer = new ServerE2E()
cyServer = new ServerBase()
return cyServer.open(config, {
SocketCtor: SocketE2E,

View File

@@ -6,7 +6,7 @@ const pkg = require('@packages/root')
const Fixtures = require('@tooling/system-tests')
const { sinon } = require('../spec_helper')
const config = require(`../../lib/config`)
const { ServerE2E } = require(`../../lib/server-e2e`)
const { ServerBase } = require(`../../lib/server-base`)
const { ProjectBase } = require(`../../lib/project-base`)
const { Automation } = require(`../../lib/automation`)
const savedState = require(`../../lib/saved_state`)
@@ -319,8 +319,8 @@ This option will not have an effect in Some-other-name. Tests that rely on web s
beforeEach(function () {
sinon.stub(this.project, 'startWebsockets')
sinon.stub(this.project, 'getConfig').returns(this.config)
sinon.stub(ServerE2E.prototype, 'open').resolves([])
sinon.stub(ServerE2E.prototype, 'reset')
sinon.stub(ServerBase.prototype, 'open').resolves([])
sinon.stub(ServerBase.prototype, 'reset')
})
it('calls #startWebsockets with options + config', function () {

View File

@@ -6,7 +6,7 @@ const express = require('express')
const Promise = require('bluebird')
const { connect } = require('@packages/network')
const { setupFullConfigWithDefaults } = require('@packages/config')
const { ServerE2E } = require(`../../lib/server-e2e`)
const { ServerBase } = require(`../../lib/server-base`)
const { SocketE2E } = require(`../../lib/socket-e2e`)
const fileServer = require(`../../lib/file_server`)
const ensureUrl = require(`../../lib/util/ensure-url`)
@@ -20,7 +20,7 @@ mockery.registerMock('morgan', () => {
describe('lib/server', () => {
beforeEach(function () {
this.server = new ServerE2E()
this.server = new ServerBase()
return setupFullConfigWithDefaults({ projectRoot: '/foo/bar/', config: { supportFile: false } }, getCtx().file.getFilesByGlob)
.then((cfg) => {
@@ -54,7 +54,7 @@ describe.skip('lib/server', () => {
return setupFullConfigWithDefaults({ projectRoot: '/foo/bar/' }, getCtx().file.getFilesByGlob)
.then((cfg) => {
this.config = cfg
this.server = new ServerE2E()
this.server = new ServerBase()
this.oldFileServer = this.server._fileServer
this.server._fileServer = this.fileServer

View File

@@ -9,7 +9,7 @@ const Fixtures = require('@tooling/system-tests')
const errors = require('../../lib/errors')
const { SocketE2E } = require('../../lib/socket-e2e')
const { ServerE2E } = require('../../lib/server-e2e')
const { ServerBase } = require('../../lib/server-base')
const { Automation } = require('../../lib/automation')
const preprocessor = require('../../lib/plugins/preprocessor')
const { fs } = require('../../lib/util/fs')
@@ -37,7 +37,7 @@ describe('lib/socket', () => {
this.todosPath = Fixtures.projectPath('todos')
this.server = new ServerE2E()
this.server = new ServerBase()
await ctx.actions.project.setCurrentProjectAndTestingTypeForTestSetup(this.todosPath)

View File

@@ -126,7 +126,7 @@ export const getCommonConfig = () => {
net: false,
os: require.resolve('os-browserify/browser'),
path: require.resolve('path-browserify'),
process: require.resolve('process/browser'),
process: require.resolve('process/browser.js'),
stream: require.resolve('stream-browserify'),
tls: false,
url: require.resolve('url/'),
@@ -240,7 +240,15 @@ export const getCommonConfig = () => {
// @see https://gist.github.com/ef4/d2cf5672a93cf241fd47c020b9b3066a#polyfilling-globals
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser',
// As of Webpack 5, a new option called resolve.fullySpecified, was added.
// This option means that a full path, in particular to .mjs / .js files
// in ESM packages must have the full path of an import specified.
// Otherwise, compilation fails as this option defaults to true.
// This means we need to adjust our global injections to always
// resolve to include the full file extension if a file resolution is provided.
// @see https://github.com/cypress-io/cypress/issues/27599
// @see https://webpack.js.org/configuration/module/#resolvefullyspecified
process: 'process/browser.js',
}),
...(liveReloadEnabled ? [new LiveReloadPlugin({ appendScriptTag: true, port: 0, hostname: 'localhost', protocol: 'http' })] : []),
],
@@ -265,7 +273,7 @@ export const getSimpleConfig = () => ({
net: false,
os: require.resolve('os-browserify/browser'),
path: require.resolve('path-browserify'),
process: require.resolve('process/browser'),
process: require.resolve('process/browser.js'),
stream: require.resolve('stream-browserify'),
tls: false,
url: require.resolve('url/'),

View File

@@ -4088,13 +4088,11 @@
"./packages/server/lib/project_utils.ts",
"./packages/server/lib/remote_states.ts",
"./packages/server/lib/request.js",
"./packages/server/lib/routes-ct.ts",
"./packages/server/lib/routes-e2e.ts",
"./packages/server/lib/routes.ts",
"./packages/server/lib/saved_state.ts",
"./packages/server/lib/server-base.ts",
"./packages/server/lib/server-ct.ts",
"./packages/server/lib/server-e2e.ts",
"./packages/server/lib/server-base.ts",
"./packages/server/lib/session.ts",
"./packages/server/lib/socket-base.ts",
"./packages/server/lib/socket-e2e.ts",

View File

@@ -4087,13 +4087,11 @@
"./packages/server/lib/project_utils.ts",
"./packages/server/lib/remote_states.ts",
"./packages/server/lib/request.js",
"./packages/server/lib/routes-ct.ts",
"./packages/server/lib/routes-e2e.ts",
"./packages/server/lib/routes.ts",
"./packages/server/lib/saved_state.ts",
"./packages/server/lib/server-base.ts",
"./packages/server/lib/server-ct.ts",
"./packages/server/lib/server-e2e.ts",
"./packages/server/lib/server-base.ts",
"./packages/server/lib/session.ts",
"./packages/server/lib/socket-base.ts",
"./packages/server/lib/socket-e2e.ts",

View File

@@ -4085,13 +4085,11 @@
"./packages/server/lib/project_utils.ts",
"./packages/server/lib/remote_states.ts",
"./packages/server/lib/request.js",
"./packages/server/lib/routes-ct.ts",
"./packages/server/lib/routes-e2e.ts",
"./packages/server/lib/routes.ts",
"./packages/server/lib/saved_state.ts",
"./packages/server/lib/server-base.ts",
"./packages/server/lib/server-ct.ts",
"./packages/server/lib/server-e2e.ts",
"./packages/server/lib/server-base.ts",
"./packages/server/lib/session.ts",
"./packages/server/lib/socket-base.ts",
"./packages/server/lib/socket-e2e.ts",