-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: patch fetch and xhr inside cy.origin to get resourceType and cr…
…edential Level (#23822) * chore: modify xhr-fetch-requests to handle onload and prep for use in patches tests * feat: add patches for fetch and xmlhttprequest * chore: short circuit fetch and xmlHttpRequests if conditions aren't met * chore: refactor xmlHttpRequest and fetch patches into individual files and add some basic types * chore: fix typo
- Loading branch information
1 parent
0c26563
commit f356065
Showing
10 changed files
with
777 additions
and
26 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
109 changes: 109 additions & 0 deletions
109
packages/driver/cypress/fixtures/xhr-fetch-requests.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<h1 data-cy="assertion-header">Making XHR and Fetch Requests behind the scenes if fireOnload is true!</h1> | ||
<button data-cy="trigger-fetch" onclick="triggerFetch('/test-request')"> trigger fetch </button> | ||
<button data-cy="trigger-fetch-with-request-object" onclick="triggerFetchWithRequestObject('/test-request')"> trigger fetch with Request Object</button> | ||
<button data-cy="trigger-fetch-with-url-object" onclick="triggerFetchWithUrlObject('/test-request')" > trigger fetch with URL Object</button> | ||
<button data-cy="trigger-fetch-omit" onclick="triggerFetch('/test-request', 'omit')"> trigger fetch w/ omit credentials </button> | ||
<button data-cy="trigger-fetch-with-request-object-omit" onclick="triggerFetchWithRequestObject('/test-request', 'omit')"> trigger fetch with Request Object w/ omit credentials</button> | ||
<button data-cy="trigger-fetch-with-url-object-omit" onclick="triggerFetchWithUrlObject('/test-request', 'omit')" > trigger fetch with URL Object w/ omit credentials</button> | ||
<button data-cy="trigger-fetch-include" onclick="triggerFetch('/test-request', 'include')"> trigger fetch w/ include credentials </button> | ||
<button data-cy="trigger-fetch-with-request-object-include" onclick="triggerFetchWithRequestObject('/test-request', 'include')"> trigger fetch with Request Object w/ include credentials</button> | ||
<button data-cy="trigger-fetch-with-url-object-include" onclick="triggerFetchWithUrlObject('/test-request', 'include')" > trigger fetch with URL Object w/ include credentials</button> | ||
<button data-cy="trigger-fetch-with-bad-options" onclick="triggerFetch(null)">trigger fetch with bad option</button> | ||
<button data-cy="trigger-fetch-with-preflight" onclick="triggerFailingFetchPreflight('/test-request')">trigger fetch w/ preflight</button> | ||
<button data-cy="trigger-xml-http-request" onclick="triggerXmlHttpRequest('/test-request')">trigger xmlHttpRequest</button> | ||
<button data-cy="trigger-xml-http-request-with-credentials" onclick="triggerXmlHttpRequest('/test-request', true)">trigger xmlHttpRequest w/ credentials</button> | ||
<button data-cy="trigger-xml-http-request-with-bad-options" onclick="triggerXmlHttpRequest(null)">trigger xmlHttpRequest w/ bad options</button> | ||
<button data-cy="trigger-xml-http-request-with-preflight" onclick="triggerFailingXmlHttpRequestPreflight('/test-request')">trigger xmlHttpRequest w/ preflight</button> | ||
<script> | ||
function triggerFetch(requestOrUrlObjOrString, credentials){ | ||
let fetchReq | ||
if(credentials){ | ||
fetchReq = fetch(requestOrUrlObjOrString, { | ||
credentials | ||
}) | ||
} else { | ||
fetchReq = fetch(requestOrUrlObjOrString) | ||
} | ||
return fetchReq.then(function(response) { | ||
// throw errors in our application to test when fetch fails | ||
if (!response.ok) { | ||
throw Error(response.status); | ||
} | ||
return response; | ||
}) | ||
} | ||
|
||
function triggerFetchWithRequestObject(urlString, credentials){ | ||
let req = new Request(urlString) | ||
if(credentials){ | ||
// credentials must either match options passed into fetch or must exist on the Request object itself | ||
req = new Request(urlString, { | ||
credentials | ||
}) | ||
} | ||
return triggerFetch(req) | ||
} | ||
|
||
function triggerFetchWithUrlObject(urlString, credentials){ | ||
return triggerFetch(new URL(urlString, window.location.origin), credentials) | ||
} | ||
|
||
function triggerFailingFetchPreflight(relativeUrl){ | ||
let url = new URL(relativeUrl, 'http://app.foobar.com:3500').toString() | ||
return fetch(url, { | ||
headers: { | ||
'foo': 'bar' | ||
}, | ||
credentials: 'include' | ||
}).catch(() => { | ||
throw new Error('CORS ERROR'); | ||
}).then(() => { | ||
throw new Error('request succeeded when it shouldn\'t have') | ||
}) | ||
} | ||
|
||
function triggerXmlHttpRequest(relativeUrl, withCredentials = false){ | ||
const url = new URL(relativeUrl, window.location.origin) | ||
xhr = new XMLHttpRequest(); | ||
xhr.open("GET", url); | ||
xhr.withCredentials = withCredentials | ||
xhr.send(); | ||
} | ||
|
||
function triggerFailingXmlHttpRequestPreflight(relativeUrl){ | ||
// might need a cross origin req here | ||
let url = new URL(relativeUrl, 'http://app.foobar.com:3500').toString() | ||
|
||
let xhr = new XMLHttpRequest() | ||
|
||
xhr.open('GET', url) | ||
// adding headers to trigger a preflight request. @see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests | ||
xhr.setRequestHeader('foo', 'bar') | ||
// since the plugin server sets the Access-Control-Allow-Origin to * (wildcard), this request will fail as a CORS errors. The | ||
xhr.withCredentials = true | ||
|
||
xhr.onerror = function () { | ||
throw new Error('CORS ERROR') | ||
} | ||
|
||
xhr.send() | ||
} | ||
|
||
function fireXHRAndFetchRequests() { | ||
if(window.location.search.includes('fireOnload=true')){ | ||
xhr = new XMLHttpRequest(); | ||
xhr.open("GET", "http://localhost:3500/foo.bar.baz.json"); | ||
xhr.responseType = "json"; | ||
xhr.send(); | ||
|
||
fetch("http://localhost:3500/foo.bar.baz.json") | ||
} | ||
} | ||
|
||
fireXHRAndFetchRequests() | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { captureFullRequestUrl } from './utils' | ||
|
||
export const patchFetch = (Cypress: Cypress.Cypress, window) => { | ||
// if fetch is available in the browser, or is polyfilled by whatwg fetch | ||
// intercept method calls and add cypress headers to determine cookie applications in the proxy | ||
// for simulated top. @see https://github.github.io/fetch/ for default options | ||
if (!Cypress.config('experimentalSessionAndOrigin') || !window.fetch) { | ||
return | ||
} | ||
|
||
const originalFetch = window.fetch | ||
|
||
window.fetch = function (...args) { | ||
try { | ||
let url: string | undefined = undefined | ||
let credentials: string | undefined = undefined | ||
|
||
const resource = args[0] | ||
|
||
// @see https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters for fetch resource options. We will only support Request, URL, and strings | ||
if (resource instanceof window.Request) { | ||
({ url, credentials } = resource) | ||
} else if (resource instanceof window.URL) { | ||
// should be a no-op for URL | ||
url = resource.toString() | ||
|
||
;({ credentials } = args[1] || {}) | ||
} else if (Cypress._.isString(resource)) { | ||
url = captureFullRequestUrl(resource, window) | ||
|
||
;({ credentials } = args[1] || {}) | ||
} | ||
|
||
credentials = credentials || 'same-origin' | ||
// if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies | ||
// if the option isn't set, we can imply the default as we know the resource type in the proxy | ||
if (url) { | ||
// @ts-expect-error | ||
Cypress.backend('request:sent:with:credentials', { | ||
// TODO: might need to go off more information here or at least make collisions less likely | ||
url, | ||
resourceType: 'fetch', | ||
credentialStatus: credentials, | ||
}) | ||
} | ||
} finally { | ||
// if our internal logic errors for whatever reason, do NOT block the end user and continue the request | ||
return originalFetch.apply(this, args) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const captureFullRequestUrl = (relativeOrAbsoluteUrlString: string, window: Window) => { | ||
// need to pass the window here by reference to generate the correct absolute URL if needed. Spec Bridge does NOT contain sub domain | ||
let url | ||
|
||
try { | ||
url = new URL(relativeOrAbsoluteUrlString).toString() | ||
} catch (err1) { | ||
try { | ||
// likely a relative path, construct the full url | ||
url = new URL(relativeOrAbsoluteUrlString, window.location.origin).toString() | ||
} catch (err2) { | ||
return undefined | ||
} | ||
} | ||
|
||
return url | ||
} |
42 changes: 42 additions & 0 deletions
42
packages/driver/src/cross-origin/patches/xmlHttpRequest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { captureFullRequestUrl } from './utils' | ||
|
||
export const patchXmlHttpRequest = (Cypress: Cypress.Cypress, window: Window) => { | ||
// intercept method calls and add cypress headers to determine cookie applications in the proxy | ||
// for simulated top | ||
|
||
if (!Cypress.config('experimentalSessionAndOrigin')) { | ||
return | ||
} | ||
|
||
const originalXmlHttpRequestOpen = window.XMLHttpRequest.prototype.open | ||
const originalXmlHttpRequestSend = window.XMLHttpRequest.prototype.send | ||
|
||
window.XMLHttpRequest.prototype.open = function (...args) { | ||
try { | ||
// since the send method does NOT have access to the arguments passed into open or have the request information, | ||
// we need to store a reference here to what we need in the send method | ||
this._url = captureFullRequestUrl(args[1], window) | ||
} finally { | ||
return originalXmlHttpRequestOpen.apply(this, args as any) | ||
} | ||
} | ||
|
||
window.XMLHttpRequest.prototype.send = function (...args) { | ||
try { | ||
// if the option is specified, communicate it to the the server to the proxy can make the request aware if it needs to potentially apply cross origin cookies | ||
// if the option isn't set, we can imply the default as we know the resource type in the proxy | ||
if (this._url) { | ||
// @ts-expect-error | ||
Cypress.backend('request:sent:with:credentials', { | ||
// TODO: might need to go off more information here or at least make collisions less likely | ||
url: this._url, | ||
resourceType: 'xhr', | ||
credentialStatus: this.withCredentials, | ||
}) | ||
} | ||
} finally { | ||
// if our internal logic errors for whatever reason, do NOT block the end user and continue the request | ||
return originalXmlHttpRequestSend.apply(this, args) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters