-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: patch fetch and xhr inside cy.origin to get resourceType and credential Level #23822
Merged
AtofStryker
merged 5 commits into
feature/simulated-top-cookie-handling
from
feature/patch-fetch-and-xhr
Sep 19, 2022
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
58fc6d8
chore: modify xhr-fetch-requests to handle onload and prep for use in…
AtofStryker 1e79565
feat: add patches for fetch and xmlhttprequest
AtofStryker d3bdb79
chore: short circuit fetch and xmlHttpRequests if conditions aren't met
AtofStryker 2dc3592
chore: refactor xmlHttpRequest and fetch patches into individual file…
AtofStryker 3f1d5da
chore: fix typo
AtofStryker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AtofStryker Given @mjhenkes' changes will be landing soon to remove the localBut events for cross-origin, do you expect this will event will continue to emit as a a localBut event once that merges?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes everything should still emit as normal since the localBus is used heavily for automation tasks and we still have the setup in the server for the
cross:origin:automation:cookies
events