Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3f5b69c
automatically polyfill window.fetch with XMLHttpRequest
bahmutov Jun 5, 2020
414cdea
yarn lock
bahmutov Jun 5, 2020
969069f
Merge branch 'develop' into polyfill-fetch
bahmutov Jun 5, 2020
254cc50
Revert "yarn lock"
bahmutov Jun 5, 2020
81e97aa
Merge branch 'develop' into polyfill-fetch
bahmutov Jun 5, 2020
5b579ea
yarn lock for has-binary2
bahmutov Jun 5, 2020
eb4ea44
add test polyfill recipe PR
bahmutov Jun 5, 2020
f96bdeb
Merge branch 'develop' into polyfill-fetch
bahmutov Jun 5, 2020
2ce27ce
removing eval, using this.XMLHttpRequest to inject proper XMLHttpRequ…
JessicaSachs Jun 5, 2020
8f7c9a9
fixing build
JessicaSachs Jun 5, 2020
89cda9a
dev patches
JessicaSachs Jun 5, 2020
0990b4e
try patching differently, doubt it would work
bahmutov Jun 5, 2020
dbbd1a0
merge develop
bahmutov Jun 9, 2020
60063ab
last merge conflict
bahmutov Jun 9, 2020
f2e8988
Merge branch 'develop' into polyfill-fetch
bahmutov Jun 9, 2020
89d13a8
Merge branch 'develop' into polyfill-fetch
bahmutov Jun 9, 2020
30bba54
bind fetch polyfill to content window
bahmutov Jun 9, 2020
10ae257
remake unfetch patch
bahmutov Jun 9, 2020
7902378
Merge branch 'polyfill-fetch' of github.com:cypress-io/cypress into p…
bahmutov Jun 9, 2020
2a85c31
add e2e tests for fetch polyfill
bahmutov Jun 9, 2020
9f59760
switch to fetch constructor away from this
bahmutov Jun 9, 2020
d6aa5b7
update patch file
bahmutov Jun 9, 2020
4494913
Update packages/driver/README.md
bahmutov Jun 9, 2020
6aee0c7
Merge branch 'develop' into polyfill-fetch
bahmutov Jun 10, 2020
2f2e2b7
removed unfetch from root level
bahmutov Jun 10, 2020
d318fb7
adding experimentalFetchPolyfill
bahmutov Jun 10, 2020
135e262
only drop fetch polyfill if experimentalFetchPolyfill is true
bahmutov Jun 10, 2020
4386f7b
enable experimentalFetchPolyfill in the e2e test
bahmutov Jun 10, 2020
df44bdb
set state and add test for no polyfill
bahmutov Jun 10, 2020
28f3e03
add experimentalFetchPolyfill to cypress.json schema
bahmutov Jun 10, 2020
62a8b53
update unit test
bahmutov Jun 10, 2020
05e89b1
Merge branch 'develop' into polyfill-fetch
bahmutov Jun 15, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1273,9 +1273,8 @@ jobs:
- test-binary-against-repo:
repo: cypress-example-recipes
command: npm run test:ci
# https://github.com/cypress-io/cypress-example-recipes/pull/501
pull_request_id: 501
folder: examples/unit-testing__lit-element
pull_request_id: 503
folder: examples/stubbing-spying__window-fetch

"test-binary-against-kitchensink":
<<: *defaults
Expand Down Expand Up @@ -1590,11 +1589,11 @@ linux-workflow: &linux-workflow
- build-binary
- build-npm-package
- test-binary-against-recipe-pull-request:
name: Shadow DOM recipe
name: Test fetch polyfill
filters:
branches:
only:
- pull/7469
- polyfill-fetch
requires:
- build-binary
- build-npm-package
Expand Down
5 changes: 5 additions & 0 deletions cli/schema/cypress.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@
"type": "boolean",
"default": false,
"description": "Enables shadow DOM support. Adds the `cy.shadow()` command and the `ignoreShadowBoundaries` option to some DOM commands."
},
"experimentalFetchPolyfill": {
"type": "boolean",
"default": false,
"description": "Polyfills `window.fetch` to enable Network spying and stubbing"
}
}
}
7 changes: 7 additions & 0 deletions packages/driver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ In the browser
localStorage.debug = "cypress:driver"
```

## Patches

- `sinon`
- `unfetch` to polyfill `fetch`. Added constructor function to point XMLHttpRequest to the application under test window.

Note: when creating a patch, make sure there is no `package-lock.json` file! Also rename the patch to have ".dev.patch" extension.

<!-- ## Catalog of Events

TODO: this data is accurate but also somewhat out of date.
Expand Down
1 change: 1 addition & 0 deletions packages/driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"text-mask-addons": "3.8.0",
"underscore": "1.9.1",
"underscore.string": "3.3.5",
"unfetch": "4.1.0",
"url-parse": "1.4.7",
"vanilla-text-mask": "5.1.1",
"wait-on": "3.3.0",
Expand Down
119 changes: 119 additions & 0 deletions packages/driver/patches/unfetch+4.1.0.dev.patch

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions packages/driver/src/cypress/cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const $CommandQueue = require('./command_queue')
const $VideoRecorder = require('../cy/video-recorder')
const $TestConfigOverrides = require('../cy/testConfigOverrides')

const { registerFetch } = require('unfetch')

const privateProps = {
props: { name: 'state', url: true },
privates: { name: 'state', url: false },
Expand Down Expand Up @@ -258,6 +260,14 @@ const create = function (specWindow, Cypress, Cookies, state, config, log) {

contentWindow.CSSStyleSheet.prototype.insertRule = _.wrap(insertRule, cssModificationSpy)
contentWindow.CSSStyleSheet.prototype.deleteRule = _.wrap(deleteRule, cssModificationSpy)

if (config('experimentalFetchPolyfill')) {
// drop "fetch" polyfill that replaces it with XMLHttpRequest
// from the app iframe that we wrap for network stubbing
contentWindow.fetch = registerFetch(contentWindow)
// flag the polyfill to test this experimental feature easier
state('fetchPolyfilled', true)
}
} catch (error) {} // eslint-disable-line no-empty
}

Expand Down
10 changes: 9 additions & 1 deletion packages/server/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ browsers\
// Know experimental flags / values
// each should start with "experimental" and be camel cased
// example: experimentalComponentTesting
const experimentalConfigKeys = ['experimentalGetCookiesSameSite', 'experimentalSourceRewriting', 'experimentalComponentTesting', 'experimentalShadowDomSupport']
const experimentalConfigKeys = toWords(`\
experimentalGetCookiesSameSite
experimentalSourceRewriting
experimentalComponentTesting
experimentalShadowDomSupport
experimentalFetchPolyfill\
`)

const CONFIG_DEFAULTS = {
port: null,
Expand Down Expand Up @@ -171,6 +177,7 @@ const CONFIG_DEFAULTS = {
experimentalGetCookiesSameSite: false,
experimentalSourceRewriting: false,
experimentalShadowDomSupport: false,
experimentalFetchPolyfill: false,
}

const validationRules = {
Expand Down Expand Up @@ -218,6 +225,7 @@ const validationRules = {
experimentalGetCookiesSameSite: v.isBoolean,
experimentalSourceRewriting: v.isBoolean,
experimentalShadowDomSupport: v.isBoolean,
experimentalFetchPolyfill: v.isBoolean,
}

const convertRelativeToAbsolutePaths = (projectRoot, obj, defaults = {}) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/server/lib/experiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const _summaries: StringValues = {
experimentalSourceRewriting: 'Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm.',
experimentalGetCookiesSameSite: 'Adds `sameSite` values to the objects yielded from `cy.setCookie()`, `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0.',
experimentalShadowDomSupport: 'Enables support for shadow DOM traversal, introduces the `shadow()` command and the `ignoreShadowBoundaries` option to traversal commands.',
experimentalFetchPolyfill: 'Polyfills `window.fetch` to enable Network spying and stubbing',
}

/**
Expand All @@ -72,6 +73,7 @@ const _names: StringValues = {
experimentalSourceRewriting: 'Improved source rewriting',
experimentalGetCookiesSameSite: 'Set `sameSite` property when retrieving cookies',
experimentalShadowDomSupport: 'Shadow DOM Support',
experimentalFetchPolyfill: 'Fetch polyfill',
}

/**
Expand Down
162 changes: 162 additions & 0 deletions packages/server/test/e2e/8_fetch_polyfill_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
const e2e = require('../support/helpers/e2e').default
const { stripIndent } = require('common-tags')
const bodyParser = require('body-parser')

const onServer = function (app) {
app.use(bodyParser.json({ extended: false }))

app.get('/get-json', (req, res) => {
return res.json([1, 2, 3])
})

app.get('/get-text', (req, res) => {
return res.send('pong')
})

app.post('/add', (req, res) => {
if (req.body.method !== 'add') {
throw new Error('wrong body method')
}

return res.json({ answer: req.body.a + req.body.b })
})

// page posts a JSON object
app.get('/addition', (req, res) => {
return res.send(stripIndent`
<body>
<div id="result"></div>
<script>
const data = {
method: 'add',
a: 2,
b: 15
}
fetch('/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then((response) => {
if (!response) {
throw new Error('no response')
}
if (!response.ok) {
throw new Error('response is not ok')
}
return response.json()
}).then(j => {
// either answer from the server
// or from the network stub
if (j.answer !== 17 && j.answer !== 193) {
throw new Error('wrong answer')
}
document.getElementById('result').innerText = 'answer: ' + j.answer
})
</script>
</body>
`)
})

// page fetches JSON array
app.get('/first', (req, res) => {
return res.send(stripIndent`
<body>
<script>
fetch('/get-json')
.then((response) => {
if (!response) {
throw new Error('no response')
}
if (!response.ok) {
throw new Error('response is not ok')
}
return response.json()
})
.then((list) => {
if (list.length !== 3) {
throw new Error('Wrong number of items')
}
if (list[0] !== 1) {
throw new Error('Wrong first item')
}
if (list[1] !== 2) {
throw new Error('Wrong second item')
}
if (list[2] !== 3) {
throw new Error('Wrong third item')
}
})
</script>
<a href="/second">second</a>
</body>
`)
})

// page fetches text
app.get('/second', (req, res) => {
return res.send(stripIndent`
<body>
<div id="result"></div>
<script>
fetch('/get-text')
.then((response) => {
if (!response) {
throw new Error('no response')
}
if (!response.ok) {
throw new Error('response is not ok')
}
if (response.status !== 200) {
throw new Error('response status not 200')
}
return response.text()
})
.then((text) => {
// allow response from the server
// or stub response
if (text !== 'pong' && text !== 'mock pong') {
throw new Error('Wrong text response')
}
document.getElementById('result').innerText = 'text: ' + text
})
</script>
</body>
`)
})
}

describe('e2e fetch polyfill', () => {
e2e.setup({
servers: {
port: 1818,
onServer,
},
})

e2e.it('passes', {
spec: 'fetch_spec.coffee',
snapshot: false,
config: {
experimentalFetchPolyfill: true,
},
})
})

describe('e2e no fetch polyfill', () => {
e2e.setup({
servers: {
port: 1818,
onServer,
},
})

e2e.it('passes', {
spec: 'fetch_no_polyfill_spec.coffee',
snapshot: false,
config: {
experimentalFetchPolyfill: false,
},
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe "without fetch polyfill", ->
it "does not set polyfilled state", ->
cy.visit("http://localhost:1818/first")
.then ->
expect(cy.state("fetchPolyfilled")).to.be.undefined
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
describe "fetch works", ->
it "sets polyfilled state", ->
cy.visit("http://localhost:1818/first")
.then ->
expect(cy.state("fetchPolyfilled")).to.be.true

it "spies on fetch requests", ->
cy.server()
cy.route("/get-json").as("get-json")
cy.visit("http://localhost:1818/first")
cy.wait("@get-json")

it "spies on fetch requests from second page", ->
cy.server()
cy.route("/get-text").as("get-text")
cy.visit("http://localhost:1818/first")
cy.contains("a", "second").click()
cy.wait("@get-text")
# confirm the real response from the server is shown
cy.contains("text: pong").should("be.visible")

it "stubs GET fetch text", ->
cy.server()
cy.route("/get-text", "mock pong")
cy.visit("http://localhost:1818/second")
# confirm the stub response is shown
cy.contains("text: mock pong").should("be.visible")

it "spies on fetch POST", ->
cy.server()
cy.route("POST", "/add").as("add")
cy.visit("http://localhost:1818/addition")
cy.wait("@add")
# confirm the response from the server is displayed
cy.contains("answer: 17").should('be.visible')

it "stubs fetch POST", ->
cy.server()
cy.route("POST", "/add", {answer: 193}).as("add")
cy.visit("http://localhost:1818/addition")
cy.wait("@add")
# confirm the stub was used
cy.contains("answer: 193").should('be.visible')

2 changes: 2 additions & 0 deletions packages/server/test/unit/config_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,7 @@ describe('lib/config', () => {
experimentalComponentTesting: { value: false, from: 'default' },
componentFolder: { value: 'cypress/component', from: 'default' },
experimentalShadowDomSupport: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
})
})
})
Expand Down Expand Up @@ -1213,6 +1214,7 @@ describe('lib/config', () => {
experimentalComponentTesting: { value: false, from: 'default' },
componentFolder: { value: 'cypress/component', from: 'default' },
experimentalShadowDomSupport: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
env: {
foo: {
value: 'foo',
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24682,6 +24682,11 @@ undertaker@^1.2.1:
object.reduce "^1.0.0"
undertaker-registry "^1.0.0"

[email protected]:
version "4.1.0"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.1.0.tgz#6ec2dd0de887e58a4dee83a050ded80ffc4137db"
integrity sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==

unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
Expand Down