-
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
fix: move main tab activation to puppeteer plugin #28898
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
7fdb706
fix: move main tab activation to puppeteer plugin
cacieprins 076b115
Merge branch 'develop' into cacie/fix/main-tab-reactivation-hang
cacieprins 6a47ba0
tests for new url functionality in v3 extension
cacieprins d6becba
tests for activateMainTab
cacieprins 72c4e24
tests
cacieprins 409eab7
Merge branch 'develop' into cacie/fix/main-tab-reactivation-hang
cacieprins aafbe8d
cleanup
cacieprins 20e695b
add troubleshooting to puppeteer plugin readme re: chrome extension
cacieprins f6fc4f0
changelog
cacieprins 7f72ecc
Merge branch 'develop' into cacie/fix/main-tab-reactivation-hang
cacieprins b413c39
no longer attempts to activate main tab in run mode
cacieprins e85ec1f
Update npm/puppeteer/README.md
cacieprins 7fac272
Update cli/CHANGELOG.md
cacieprins 651ad10
Merge branch 'develop' into cacie/fix/main-tab-reactivation-hang
cacieprins e0974e4
Merge branch 'develop' into cacie/fix/main-tab-reactivation-hang
cacieprins 791b73b
Merge branch 'develop' into cacie/fix/main-tab-reactivation-hang
cacieprins 948c377
Merge branch 'develop' into cacie/fix/main-tab-reactivation-hang
cacieprins 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
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 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,46 @@ | ||
/// <reference lib="browser"> | ||
import type { Browser } from 'puppeteer-core' | ||
|
||
export const ACTIVATION_TIMEOUT = 2000 | ||
|
||
const sendActivationMessage = (activationTimeout: number) => { | ||
// don't need to worry about tabs for Cy in Cy tests | ||
if (document.defaultView !== top) { | ||
return | ||
} | ||
|
||
let timeout: NodeJS.Timeout | ||
let onMessage: (ev: MessageEvent) => void | ||
|
||
// promise must resolve with a value for chai as promised to test resolution | ||
return new Promise<void>((resolve, reject) => { | ||
onMessage = (ev) => { | ||
if (ev.data.message === 'cypress:extension:main:tab:activated') { | ||
window.removeEventListener('message', onMessage) | ||
clearTimeout(timeout) | ||
resolve() | ||
} | ||
} | ||
|
||
window.addEventListener('message', onMessage) | ||
window.postMessage({ message: 'cypress:extension:activate:main:tab' }) | ||
|
||
timeout = setTimeout(() => { | ||
window.removeEventListener('message', onMessage) | ||
reject() | ||
}, activationTimeout) | ||
}) | ||
} | ||
|
||
export const activateMainTab = async (browser: Browser) => { | ||
// - Only implemented for Chromium right now. Support for Firefox/webkit | ||
// could be added later | ||
// - Electron doesn't have tabs | ||
// - Focus doesn't matter for headless browsers and old headless Chrome | ||
// doesn't run the extension | ||
const [page] = await browser.pages() | ||
|
||
if (page) { | ||
return page.evaluate(sendActivationMessage, ACTIVATION_TIMEOUT) | ||
} | ||
} |
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,118 @@ | ||
import { expect, use } from 'chai' | ||
import chaiAsPromised from 'chai-as-promised' | ||
import sinon from 'sinon' | ||
import sinonChai from 'sinon-chai' | ||
import type { Browser, Page } from 'puppeteer-core' | ||
import { activateMainTab, ACTIVATION_TIMEOUT } from '../../src/plugin/activateMainTab' | ||
|
||
use(chaiAsPromised) | ||
use(sinonChai) | ||
|
||
describe('activateMainTab', () => { | ||
let clock: sinon.SinonFakeTimers | ||
let prevWin: Window | ||
let prevDoc: Document | ||
let prevTop: Window & typeof globalThis | ||
let window: Partial<Window> | ||
let mockDocument: Partial<Document> & { | ||
defaultView: Window & typeof globalThis | ||
} | ||
let mockTop: Partial<Window & typeof globalThis> | ||
let mockBrowser: Partial<Browser> | ||
let mockPage: Partial<Page> | ||
|
||
beforeEach(() => { | ||
clock = sinon.useFakeTimers() | ||
|
||
window = { | ||
addEventListener: sinon.stub(), | ||
removeEventListener: sinon.stub(), | ||
|
||
// @ts-ignore sinon gets confused about postMessage type declaration | ||
postMessage: sinon.stub(), | ||
} | ||
|
||
mockDocument = { | ||
defaultView: window as Window & typeof globalThis, | ||
} | ||
|
||
mockTop = mockDocument.defaultView | ||
|
||
// activateMainTab is eval'd in browser context, but the tests exec in a | ||
// node context. We don't necessarily need to do this swap, but it makes the | ||
// tests more portable. | ||
// @ts-ignore | ||
prevWin = global.window | ||
prevDoc = global.document | ||
// @ts-ignore | ||
prevTop = global.top | ||
//@ts-ignore | ||
global.window = window | ||
global.document = mockDocument as Document | ||
//@ts-ignore | ||
global.top = mockTop | ||
|
||
mockPage = { | ||
evaluate: sinon.stub().callsFake((fn, ...args) => fn(...args)), | ||
} | ||
|
||
mockBrowser = { | ||
pages: sinon.stub(), | ||
} | ||
}) | ||
|
||
afterEach(() => { | ||
clock.restore() | ||
// @ts-ignore | ||
global.window = prevWin | ||
// @ts-ignore | ||
global.top = prevTop | ||
global.document = prevDoc | ||
}) | ||
|
||
it('sends a tab activation request to the plugin, and resolves when the ack event is received', async () => { | ||
const pagePromise = Promise.resolve([mockPage]) | ||
|
||
;(mockBrowser.pages as sinon.SinonStub).returns(pagePromise) | ||
const p = activateMainTab(mockBrowser as Browser) | ||
|
||
await pagePromise | ||
// @ts-ignore | ||
window.addEventListener.withArgs('message').yield({ data: { message: 'cypress:extension:main:tab:activated' } }) | ||
expect(window.postMessage).to.be.calledWith({ message: 'cypress:extension:activate:main:tab' }) | ||
|
||
expect(p).to.eventually.be.true | ||
}) | ||
|
||
it('sends a tab activation request to the plugin, and rejects if it times out', async () => { | ||
const pagePromise = Promise.resolve([mockPage]) | ||
|
||
;(mockBrowser.pages as sinon.SinonStub).returns(pagePromise) | ||
await pagePromise | ||
|
||
const p = activateMainTab(mockBrowser as Browser) | ||
|
||
clock.tick(ACTIVATION_TIMEOUT + 1) | ||
|
||
expect(p).to.be.rejected | ||
}) | ||
|
||
describe('when cy in cy', () => { | ||
beforeEach(() => { | ||
mockDocument.defaultView = {} as Window & typeof globalThis | ||
}) | ||
|
||
it('does not try to send tab activation message', async () => { | ||
const pagePromise = Promise.resolve([mockPage]) | ||
|
||
;(mockBrowser.pages as sinon.SinonStub).returns(pagePromise) | ||
|
||
const p = activateMainTab(mockBrowser as Browser) | ||
|
||
await pagePromise | ||
expect(window.postMessage).not.to.be.called | ||
expect(window.addEventListener).not.to.be.called | ||
await p | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.
Oops, something went wrong.
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.
whats the difference between
cypress:extension:main:tab:activated
andcypress:extension:activate:main:tab
?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.
cypress:extension:activate:main:tab
is sent to the extension to tell it to activate the main tab.cypress:extension:main:tab:activated
is the ack that the main tab was successfully activated.