Skip to content

Commit fedb65c

Browse files
authored
chore: (multi-domain) support multiple remote states (#20752)
1 parent 453d1ca commit fedb65c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1477
-599
lines changed

.vscode/launch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"type": "node",
66
"request": "attach",
77
"name": "Attach by Process ID",
8-
"processId": "${command:PickProcess}"
8+
"processId": "${command:PickProcess}",
9+
"continueOnAttach": true
910
},
1011
{
1112
"type": "node",

cli/types/cypress.d.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,12 @@ declare namespace Cypress {
4848
}
4949

5050
interface RemoteState {
51-
auth?: {
52-
username: string
53-
password: string
54-
}
51+
auth?: Auth
5552
domainName: string
5653
strategy: 'file' | 'http'
5754
origin: string
58-
fileServer: string
55+
fileServer: string | null
5956
props: Record<string, any>
60-
visiting: string
6157
}
6258

6359
interface Backend {
@@ -68,7 +64,6 @@ declare namespace Cypress {
6864
* @see https://on.cypress.io/firefox-gc-issue
6965
*/
7066
(task: 'firefox:force:gc'): Promise<void>
71-
(task: 'ready:for:domain'): Promise<void>
7267
(task: 'net', eventName: string, frame: any): Promise<void>
7368
}
7469

packages/driver/cypress/integration/e2e/multi-domain/commands/multi_domain_navigation.spec.ts

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,19 @@ context('multi-domain navigation', { experimentalSessionSupport: true }, () => {
248248
})
249249
})
250250

251-
// TODO: un-skip once multiple remote states are supported
252-
it.skip('supports auth options and adding auth to subsequent requests', () => {
251+
// TODO: test currently fails when redirecting
252+
it.skip('supports visit redirects', () => {
253+
cy.visit('/fixtures/multi-domain.html')
254+
cy.get('a[data-cy="dom-link"]').click()
255+
256+
cy.switchToDomain('http://www.foobar.com:3500', () => {
257+
cy.visit('/redirect?href=http://localhost:3500/fixtures/multi-domain-secondary.html')
258+
})
259+
260+
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary domain')
261+
})
262+
263+
it('supports auth options and adding auth to subsequent requests', () => {
253264
cy.switchToDomain('http://foobar.com:3500', () => {
254265
cy.visit('http://www.foobar.com:3500/basic_auth', {
255266
auth: {
@@ -260,26 +271,83 @@ context('multi-domain navigation', { experimentalSessionSupport: true }, () => {
260271

261272
cy.get('body').should('have.text', 'basic auth worked')
262273

263-
cy.window().then({ timeout: 60000 }, (win) => {
264-
return new Cypress.Promise(((resolve, reject) => {
265-
const xhr = new win.XMLHttpRequest()
274+
cy.window().then((win) => {
275+
win.location.href = 'http://www.foobar.com:3500/basic_auth'
276+
})
277+
278+
cy.get('body').should('have.text', 'basic auth worked')
279+
})
280+
281+
// attaches the auth options for the foobar domain even from another switchToDomain
282+
cy.switchToDomain('http://www.idp.com:3500', () => {
283+
cy.visit('/fixtures/multi-domain.html')
284+
285+
cy.window().then((win) => {
286+
win.location.href = 'http://www.foobar.com:3500/basic_auth'
287+
})
288+
})
289+
290+
cy.switchToDomain('http://foobar.com:3500', () => {
291+
cy.get('body').should('have.text', 'basic auth worked')
292+
})
293+
294+
cy.visit('/fixtures/multi-domain.html')
295+
296+
// attaches the auth options for the foobar domain from the top-level
297+
cy.window().then((win) => {
298+
win.location.href = 'http://www.foobar.com:3500/basic_auth'
299+
})
300+
301+
cy.switchToDomain('http://foobar.com:3500', () => {
302+
cy.get('body').should('have.text', 'basic auth worked')
303+
})
304+
})
266305

267-
xhr.open('GET', '/basic_auth')
268-
xhr.onload = function () {
269-
try {
270-
expect(this.responseText).to.include('basic auth worked')
306+
it('does not propagate the auth options across tests', (done) => {
307+
cy.intercept('/basic_auth', (req) => {
308+
req.on('response', (res) => {
309+
// clear the www-authenticate header so the browser doesn't prompt for username/password
310+
res.headers['www-authenticate'] = ''
311+
expect(res.statusCode).to.equal(401)
312+
done()
313+
})
314+
})
271315

272-
return resolve(win)
273-
} catch (err) {
274-
return reject(err)
275-
}
276-
}
316+
cy.window().then((win) => {
317+
win.location.href = 'http://www.foobar.com:3500/fixtures/multi-domain.html'
318+
})
277319

278-
return xhr.send()
279-
}))
320+
cy.switchToDomain('http://foobar.com:3500', () => {
321+
cy.window().then((win) => {
322+
win.location.href = 'http://www.foobar.com:3500/basic_auth'
280323
})
281324
})
282325
})
326+
327+
it('succeeds when visiting local file server first', { baseUrl: undefined }, () => {
328+
cy.visit('cypress/fixtures/multi-domain.html')
329+
330+
cy.switchToDomain('http://www.foobar.com:3500', () => {
331+
cy.visit('/fixtures/multi-domain-secondary.html')
332+
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary domain')
333+
})
334+
})
335+
336+
it('handles visit failures', { baseUrl: undefined }, (done) => {
337+
cy.on('fail', (e) => {
338+
expect(e.message).to.include('failed trying to load:\n\nhttp://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
339+
expect(e.message).to.include('500: Internal Server Error')
340+
341+
done()
342+
})
343+
344+
cy.intercept('*/multi-domain-secondary.html', { statusCode: 500 })
345+
346+
cy.visit('cypress/fixtures/multi-domain.html')
347+
cy.switchToDomain('http://www.foobar.com:3500', () => {
348+
cy.visit('fixtures/multi-domain-secondary.html')
349+
})
350+
})
283351
})
284352

285353
it('supports navigating through changing the window.location.href', () => {

packages/driver/cypress/integration/e2e/multi-domain/multi_domain_rerun_spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ describe('multi-domain - rerun', { }, () => {
55
cy.get('a[data-cy="multi-domain-secondary-link"]').click()
66
})
77

8-
// this test will hang without the fix for multi-domain rerun
9-
// https://github.com/cypress-io/cypress/issues/18043
108
it('successfully reruns tests', () => {
119
// @ts-ignore
1210
cy.switchToDomain('http://foobar.com:3500', () => {

packages/driver/cypress/plugins/server.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ const createApp = (port) => {
129129

130130
return res
131131
.set('WWW-Authenticate', 'Basic')
132+
.type('html')
132133
.sendStatus(401)
133134
})
134135

packages/driver/src/cy/multi-domain/index.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,8 @@ const normalizeDomain = (domain) => {
2121
export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: Cypress.State, config: Cypress.InternalConfig) {
2222
let timeoutId
2323

24-
// @ts-ignore
2524
const communicator = Cypress.multiDomainCommunicator
2625

27-
const sendReadyForDomain = () => {
28-
// lets the proxy know to allow the response for the secondary
29-
// domain html through, so the page will finish loading
30-
Cypress.backend('ready:for:domain')
31-
}
32-
3326
communicator.on('delaying:html', (request) => {
3427
// when a secondary domain is detected by the proxy, it holds it up
3528
// to provide time for the spec bridge to be set up. normally, the queue
@@ -38,10 +31,13 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
3831
// @ts-ignore
3932
cy.isAnticipatingMultiDomainFor(request.href)
4033

41-
// cy.isAnticipatingMultiDomainFor(href) will free the queue to move forward.
42-
// if the next command isn't switchToDomain, this timeout will hit and
43-
// the test will fail with a cross-origin error
44-
timeoutId = setTimeout(sendReadyForDomain, 2000)
34+
// If we haven't seen a switchToDomain and cleared the timeout within 300ms,
35+
// go ahead and inform the server 'ready:for:domain' failed and to release the
36+
// response. This typically happens during a redirect where the user does
37+
// not have a switchToDomain for the intermediary domain.
38+
timeoutId = setTimeout(() => {
39+
Cypress.backend('ready:for:domain', { failed: true })
40+
}, 300)
4541
})
4642

4743
Commands.addAll({
@@ -80,7 +76,9 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
8076

8177
const validator = new Validator({
8278
log,
83-
onFailure: sendReadyForDomain,
79+
onFailure: () => {
80+
Cypress.backend('ready:for:domain', { failed: true })
81+
},
8482
})
8583

8684
validator.validate({
@@ -101,6 +99,7 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
10199

102100
return new Bluebird((resolve, reject, onCancel) => {
103101
const cleanup = () => {
102+
Cypress.backend('cross:origin:finished', location.originPolicy)
104103
communicator.off('queue:finished', onQueueFinished)
105104
}
106105

@@ -135,7 +134,9 @@ export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
135134
communicator.once('ran:domain:fn', (details) => {
136135
const { subject, unserializableSubjectType, err, finished } = details
137136

138-
sendReadyForDomain()
137+
// lets the proxy know to allow the response for the secondary
138+
// domain html through, so the page will finish loading
139+
Cypress.backend('ready:for:domain', { originPolicy: location.originPolicy })
139140

140141
if (err) {
141142
return _reject(err)

packages/driver/src/cypress/location.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,7 @@ export class $Location {
8989
}
9090

9191
getOriginPolicy () {
92-
// origin policy is comprised of
93-
// protocol + superdomain
94-
// and subdomain is not factored in
95-
return _.compact([
96-
`${this.getProtocol()}//${this.getSuperDomain()}`,
97-
this.getPort(),
98-
]).join(':')
92+
return cors.getOriginPolicy(this.remote.href)
9993
}
10094

10195
getSuperDomain () {

packages/driver/src/multi-domain/cypress.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ const setup = (cypressConfig: Cypress.Config, env: Cypress.ObjectLike) => {
8181
handleUnsupportedAPIs(Cypress, cy)
8282

8383
cy.onBeforeAppWindowLoad = onBeforeAppWindowLoad(Cypress, cy)
84-
85-
return cy
8684
}
8785

8886
// eslint-disable-next-line @cypress/dev/arrow-body-multiline-braces

packages/driver/types/internal-types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ declare namespace Cypress {
1919
(action: 'after:screenshot', config: {})
2020
}
2121

22+
interface Backend {
23+
(task: 'ready:for:domain', args: { originPolicy?: string , failed?: boolean}): boolean
24+
(task: 'cross:origin:finished', originPolicy: string): boolean
25+
}
26+
2227
interface cy {
2328
/**
2429
* If `as` is chained to the current command, return the alias name used.

packages/network/lib/cors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,11 @@ export function urlMatchesOriginProtectionSpace (urlStr, origin) {
9999

100100
return _.startsWith(normalizedUrl, normalizedOrigin)
101101
}
102+
103+
export function getOriginPolicy (url: string) {
104+
const { port, protocol } = new URL(url)
105+
106+
// origin policy is comprised of:
107+
// protocol + superdomain + port (subdomain is not factored in)
108+
return _.compact([`${protocol}//${getSuperDomain(url)}`, port]).join(':')
109+
}

0 commit comments

Comments
 (0)