diff --git a/packages/driver/cypress/e2e/e2e/origin/user_agent_override.cy.ts b/packages/driver/cypress/e2e/e2e/origin/user_agent_override.cy.ts new file mode 100644 index 000000000000..539ccb61fabf --- /dev/null +++ b/packages/driver/cypress/e2e/e2e/origin/user_agent_override.cy.ts @@ -0,0 +1,50 @@ +// @see https://github.com/cypress-io/cypress/issues/22953 +// We modify the user agent to strip cypress and electron out of the user agent string to appear more chrome-like +// this only happens in electron and when the experimentalModifyObstructiveThirdPartyCode flag is set to true +describe('user agent override', { + browser: 'electron', +}, () => { + it('persists modified user agent after cy.visit', () => { + cy.wrap(window.navigator.userAgent).as('userAgentBefore') + cy.visit('/fixtures/primary-origin.html') + cy.wrap(window.navigator.userAgent).then((userAgentAfter) => { + cy.get('@userAgentBefore').then((userAgentBefore) => { + expect(userAgentBefore).to.equal(userAgentAfter) + }) + }) + }) + + it('persists modified user agent after cy.reload', () => { + cy.wrap(window.navigator.userAgent).as('userAgentBefore') + cy.visit('/fixtures/primary-origin.html') + cy.reload() + cy.wrap(window.navigator.userAgent).then((userAgentAfter) => { + cy.get('@userAgentBefore').then((userAgentBefore) => { + expect(userAgentBefore).to.equal(userAgentAfter) + }) + }) + }) + + it('persists modified user agent after cy.go', () => { + cy.visit('/fixtures/primary-origin.html') + cy.get('a[data-cy="cross-origin-secondary-link"]').click() + + cy.origin('http://foobar.com:3500', { + args: { + userAgentBefore: window.navigator.userAgent, + }, + }, ({ userAgentBefore }) => { + cy.visit('http://www.foobar.com:3500/fixtures/dom.html') + + cy.go('back') + cy.wrap(window.navigator.userAgent).then((userAgentAfter) => { + expect(userAgentBefore).to.equal(userAgentAfter) + }) + + cy.go('forward') + cy.wrap(window.navigator.userAgent).then((userAgentAfter) => { + expect(userAgentBefore).to.equal(userAgentAfter) + }) + }) + }) +}) diff --git a/packages/server/lib/browsers/electron.js b/packages/server/lib/browsers/electron.js index a81e894b7fd8..0f432da86184 100644 --- a/packages/server/lib/browsers/electron.js +++ b/packages/server/lib/browsers/electron.js @@ -243,6 +243,13 @@ module.exports = { if (ua) { this._setUserAgent(win.webContents, ua) + // @see https://github.com/cypress-io/cypress/issues/22953 + } else if (options.experimentalModifyObstructiveThirdPartyCode) { + const userAgent = this._getUserAgent(win.webContents) + // replace any obstructive electron user agents that contain electron or cypress references to appear more chrome-like + const modifiedNonObstructiveUserAgent = userAgent.replace(/Cypress.*?\s|[Ee]lectron.*?\s/g, '') + + this._setUserAgent(win.webContents, modifiedNonObstructiveUserAgent) } const setProxy = () => { @@ -415,6 +422,14 @@ module.exports = { return webContents.session.clearCache() }, + _getUserAgent (webContents) { + const userAgent = webContents.session.getUserAgent() + + debug('found user agent: %s', userAgent) + + return userAgent + }, + _setUserAgent (webContents, userAgent) { debug('setting user agent to:', userAgent) // set both because why not diff --git a/packages/server/lib/open_project.ts b/packages/server/lib/open_project.ts index 6aec06dd4b5d..d4f00ff2fd46 100644 --- a/packages/server/lib/open_project.ts +++ b/packages/server/lib/open_project.ts @@ -84,6 +84,7 @@ export class OpenProject { isTextTerminal: cfg.isTextTerminal, downloadsFolder: cfg.downloadsFolder, experimentalSessionAndOrigin: cfg.experimentalSessionAndOrigin, + experimentalModifyObstructiveThirdPartyCode: cfg.experimentalModifyObstructiveThirdPartyCode, }) // if we don't have the isHeaded property diff --git a/packages/server/test/unit/browsers/electron_spec.js b/packages/server/test/unit/browsers/electron_spec.js index 40d0b3a696d3..802d48415cd7 100644 --- a/packages/server/test/unit/browsers/electron_spec.js +++ b/packages/server/test/unit/browsers/electron_spec.js @@ -188,6 +188,7 @@ describe('lib/browsers/electron', () => { sinon.stub(electron, '_clearCache').resolves() sinon.stub(electron, '_setProxy').resolves() sinon.stub(electron, '_setUserAgent') + sinon.stub(electron, '_getUserAgent') }) it('sets menu.set whether or not its in headless mode', function () { @@ -470,6 +471,108 @@ describe('lib/browsers/electron', () => { }) }) }) + + describe('setUserAgent with experimentalModifyObstructiveThirdPartyCode', () => { + let userAgent + + beforeEach(function () { + userAgent = '' + electron._getUserAgent.callsFake(() => userAgent) + }) + + describe('disabled', function () { + it('does not attempt to replace the user agent', function () { + this.options.experimentalModifyObstructiveThirdPartyCode = false + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).not.to.be.called + }) + }) + }) + + describe('enabled and attempts to replace obstructive user agent string containing:', function () { + beforeEach(function () { + this.options.experimentalModifyObstructiveThirdPartyCode = true + }) + + it('does not attempt to replace the user agent if the user passes in an explicit user agent', function () { + userAgent = 'barbaz' + this.options.experimentalModifyObstructiveThirdPartyCode = false + this.options.userAgent = 'foobar' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.be.calledWith(this.win.webContents, 'foobar') + expect(electron._setUserAgent).not.to.be.calledWith(this.win.webContents, 'barbaz') + }) + }) + + it('versioned cypress', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/10.0.3 Chrome/100.0.4896.75 Electron/18.0.4 Safari/537.36' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36') + }) + }) + + it('development cypress', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/0.0.0-development Chrome/100.0.4896.75 Electron/18.0.4 Safari/537.36' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36') + }) + }) + + it('older Windows user agent', function () { + userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) electron/1.0.0 Chrome/53.0.2785.113 Electron/1.4.3 Safari/537.36' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36') + }) + }) + + it('newer Windows user agent', function () { + userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.5.00.4689 Chrome/85.0.4183.121 Electron/10.4.7 Safari/537.36' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.5.00.4689 Chrome/85.0.4183.121 Safari/537.36') + }) + }) + + it('Linux user agent', function () { + userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Typora/0.9.93 Chrome/83.0.4103.119 Electron/9.0.5 Safari/E7FBAF' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Typora/0.9.93 Chrome/83.0.4103.119 Safari/E7FBAF') + }) + }) + + it('older MacOS user agent', function () { + // this user agent containing Cypress was actually a common UA found on a website for Electron purposes... + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/8.3.0 Chrome/91.0.4472.124 Electron/13.1.7 Safari/537.36' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36') + }) + }) + + it('newer MacOS user agent', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36' + + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36') + }) + }) + }) + }) }) context('._render', () => { diff --git a/packages/types/src/config.ts b/packages/types/src/config.ts index edc1850f92bd..3ea6f4239777 100644 --- a/packages/types/src/config.ts +++ b/packages/types/src/config.ts @@ -30,7 +30,7 @@ export interface FullConfig extends Partial - & Pick // TODO: Figure out how to type this better. + & Pick // TODO: Figure out how to type this better. export interface SampleConfigFile{ status: 'changes' | 'valid' | 'skipped' | 'error'