-
Notifications
You must be signed in to change notification settings - Fork 64
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: ability to run tests in isolated environment #792
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,9 @@ const Camera = require("./camera"); | |
const clientBridge = require("./client-bridge"); | ||
const history = require("./history"); | ||
const logger = require("../utils/logger"); | ||
const { WEBDRIVER_PROTOCOL } = require("../constants/config"); | ||
const { MIN_CHROME_VERSION_SUPPORT_ISOLATION } = require("../constants/browser"); | ||
const { isSupportIsolation } = require("../utils/browser"); | ||
|
||
const OPTIONAL_SESSION_OPTS = ["transformRequest", "transformResponse"]; | ||
|
||
|
@@ -38,6 +41,8 @@ module.exports = class ExistingBrowser extends Browser { | |
await history.runGroup(this._callstackHistory, "hermione: init browser", async () => { | ||
this._addCommands(); | ||
|
||
await this._performIsolation({ sessionCaps, sessionOpts }); | ||
|
||
try { | ||
this.config.prepareBrowser && this.config.prepareBrowser(this.publicAPI); | ||
} catch (e) { | ||
|
@@ -201,6 +206,47 @@ module.exports = class ExistingBrowser extends Browser { | |
return this._config.baseUrl ? url.resolve(this._config.baseUrl, uri) : uri; | ||
} | ||
|
||
async _performIsolation({ sessionCaps, sessionOpts }) { | ||
if (!this._config.isolation) { | ||
return; | ||
} | ||
|
||
const { browserName, browserVersion = "", version = "" } = sessionCaps; | ||
const { automationProtocol } = sessionOpts; | ||
|
||
if (!isSupportIsolation(browserName, browserVersion)) { | ||
logger.warn( | ||
`WARN: test isolation works only with chrome@${MIN_CHROME_VERSION_SUPPORT_ISOLATION} and higher, ` + | ||
`but got ${browserName}@${browserVersion || version}`, | ||
); | ||
return; | ||
} | ||
|
||
const puppeteer = await this._session.getPuppeteer(); | ||
const browserCtxs = puppeteer.browserContexts(); | ||
|
||
const incognitoCtx = await puppeteer.createIncognitoBrowserContext(); | ||
const page = await incognitoCtx.newPage(); | ||
|
||
if (automationProtocol === WEBDRIVER_PROTOCOL) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Переключение между окнами нужно только в |
||
const windowIds = await this._session.getWindowHandles(); | ||
const incognitoWindowId = windowIds.find(id => id.includes(page.target()._targetId)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Тут приходится использовать приватное свойство Еще Версия wdio у нас четко зафиксирована поэтому кейсов когда это может сломаться на данный момент нет. |
||
|
||
await this._session.switchToWindow(incognitoWindowId); | ||
} | ||
|
||
for (const ctx of browserCtxs) { | ||
if (ctx.isIncognito()) { | ||
await ctx.close(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Инкогнито контексты можно сразу закрывать без необходимости явно закрывать все страницы. |
||
continue; | ||
} | ||
|
||
for (const page of await ctx.pages()) { | ||
await page.close(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Не инкогнито контексты закрыть невозможно. В данном случае не инкогнито контекстом является дефолтный контекст, который запускается при запуске браузера. Его закрыть нельзя, можно закрыть только страницы. |
||
} | ||
} | ||
} | ||
|
||
async _prepareSession() { | ||
await this._setOrientation(this.config.orientation); | ||
await this._setWindowSize(this.config.windowSize); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const MIN_CHROME_VERSION_SUPPORT_ISOLATION = 93; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { MIN_CHROME_VERSION_SUPPORT_ISOLATION } from "../constants/browser"; | ||
|
||
export const isSupportIsolation = (browserName: string, browserVersion = ""): boolean => { | ||
const browserVersionMajor = browserVersion.split(".")[0]; | ||
|
||
return browserName === "chrome" && Number(browserVersionMajor) >= MIN_CHROME_VERSION_SUPPORT_ISOLATION; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,16 @@ const Camera = require("src/browser/camera"); | |
const clientBridge = require("src/browser/client-bridge"); | ||
const logger = require("src/utils/logger"); | ||
const history = require("src/browser/history"); | ||
const { SAVE_HISTORY_MODE } = require("src/constants/config"); | ||
const { mkExistingBrowser_: mkBrowser_, mkSessionStub_ } = require("./utils"); | ||
const { SAVE_HISTORY_MODE, WEBDRIVER_PROTOCOL, DEVTOOLS_PROTOCOL } = require("src/constants/config"); | ||
const { MIN_CHROME_VERSION_SUPPORT_ISOLATION } = require("src/constants/browser"); | ||
const { | ||
mkExistingBrowser_: mkBrowser_, | ||
mkSessionStub_, | ||
mkCDPStub_, | ||
mkCDPBrowserCtx_, | ||
mkCDPPage_, | ||
mkCDPTarget_, | ||
} = require("./utils"); | ||
|
||
describe("ExistingBrowser", () => { | ||
const sandbox = sinon.sandbox.create(); | ||
|
@@ -390,6 +398,156 @@ describe("ExistingBrowser", () => { | |
}); | ||
}); | ||
|
||
describe("perform isolation", () => { | ||
let cdp, incognitoBrowserCtx, incognitoPage, incognitoTarget; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Не очень люблю создавать сущности в |
||
|
||
beforeEach(() => { | ||
incognitoTarget = mkCDPTarget_(); | ||
incognitoPage = mkCDPPage_(); | ||
incognitoPage.target.returns(incognitoTarget); | ||
|
||
incognitoBrowserCtx = mkCDPBrowserCtx_(); | ||
incognitoBrowserCtx.newPage.resolves(incognitoPage); | ||
incognitoBrowserCtx.isIncognito.returns(true); | ||
|
||
cdp = mkCDPStub_(); | ||
cdp.createIncognitoBrowserContext.resolves(incognitoBrowserCtx); | ||
|
||
session.getPuppeteer.resolves(cdp); | ||
}); | ||
|
||
describe("should do nothing if", () => { | ||
it("'isolation' option is not specified", async () => { | ||
await initBrowser_(mkBrowser_({ isolation: false })); | ||
|
||
assert.notCalled(session.getPuppeteer); | ||
assert.notCalled(logger.warn); | ||
}); | ||
|
||
it("test wasn't run in chrome", async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А как насчёт других браузеров, поддерживающих cdp? Типа Firefox/webkit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Так как коммент оставлен к тесту, то не вижу необходимости проверять другие варианты Если вопрос был в целом, то webkit вообще не поддерживает CDP. А firefox имеет поддержку только в nightly сборке. Поэтому их поддерживать не стал. |
||
const sessionCaps = { browserName: "firefox", browserVersion: "104.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.notCalled(session.getPuppeteer); | ||
}); | ||
|
||
it(`test wasn't run in chrome@${MIN_CHROME_VERSION_SUPPORT_ISOLATION} or higher`, async () => { | ||
const sessionCaps = { browserName: "chrome", browserVersion: "90.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.notCalled(session.getPuppeteer); | ||
}); | ||
}); | ||
|
||
describe("should warn that isolation doesn't work in", () => { | ||
it("chrome browser (w3c)", async () => { | ||
const sessionCaps = { browserName: "chrome", browserVersion: "90.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.calledOnceWith( | ||
logger.warn, | ||
`WARN: test isolation works only with chrome@${MIN_CHROME_VERSION_SUPPORT_ISOLATION} and higher, ` + | ||
"but got [email protected]", | ||
); | ||
}); | ||
|
||
it("chrome browser (jsonwp)", async () => { | ||
const sessionCaps = { browserName: "chrome", version: "70.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.calledOnceWith( | ||
logger.warn, | ||
`WARN: test isolation works only with chrome@${MIN_CHROME_VERSION_SUPPORT_ISOLATION} and higher, ` + | ||
"but got [email protected]", | ||
); | ||
}); | ||
}); | ||
|
||
it("should create incognito browser context", async () => { | ||
const sessionCaps = { browserName: "chrome", browserVersion: "100.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.calledOnceWithExactly(cdp.createIncognitoBrowserContext); | ||
}); | ||
|
||
it("should get current browser contexts before create incognito", async () => { | ||
const sessionCaps = { browserName: "chrome", browserVersion: "100.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.callOrder(cdp.browserContexts, cdp.createIncognitoBrowserContext); | ||
}); | ||
|
||
it("should create new page inside incognito browser context", async () => { | ||
const sessionCaps = { browserName: "chrome", browserVersion: "100.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.calledOnceWithExactly(incognitoBrowserCtx.newPage); | ||
}); | ||
|
||
describe(`in "${WEBDRIVER_PROTOCOL}" protocol`, () => { | ||
it("should switch to incognito window", async () => { | ||
incognitoTarget._targetId = "456"; | ||
session.getWindowHandles.resolves(["window_123", "window_456", "window_789"]); | ||
|
||
const sessionCaps = { browserName: "chrome", browserVersion: "100.0" }; | ||
const sessionOpts = { automationProtocol: WEBDRIVER_PROTOCOL }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps, sessionOpts }); | ||
|
||
assert.calledOnceWith(session.switchToWindow, "window_456"); | ||
assert.callOrder(incognitoBrowserCtx.newPage, session.getWindowHandles); | ||
}); | ||
}); | ||
|
||
describe(`in "${DEVTOOLS_PROTOCOL}" protocol`, () => { | ||
it("should not switch to incognito window", async () => { | ||
const sessionCaps = { browserName: "chrome", browserVersion: "100.0" }; | ||
const sessionOpts = { automationProtocol: DEVTOOLS_PROTOCOL }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps, sessionOpts }); | ||
|
||
assert.notCalled(session.getWindowHandles); | ||
assert.notCalled(session.switchToWindow); | ||
}); | ||
}); | ||
|
||
it("should close pages in default browser context", async () => { | ||
const defaultBrowserCtx = mkCDPBrowserCtx_(); | ||
const page1 = mkCDPPage_(); | ||
const page2 = mkCDPPage_(); | ||
defaultBrowserCtx.pages.resolves([page1, page2]); | ||
|
||
cdp.browserContexts.returns([defaultBrowserCtx, incognitoBrowserCtx]); | ||
|
||
const sessionCaps = { browserName: "chrome", browserVersion: "100.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.calledOnceWithExactly(page1.close); | ||
assert.calledOnceWithExactly(page2.close); | ||
assert.notCalled(incognitoPage.close); | ||
}); | ||
|
||
it("should close incognito browser context", async () => { | ||
const defaultBrowserCtx = mkCDPBrowserCtx_(); | ||
cdp.browserContexts.returns([defaultBrowserCtx, incognitoBrowserCtx]); | ||
|
||
const sessionCaps = { browserName: "chrome", browserVersion: "100.0" }; | ||
|
||
await initBrowser_(mkBrowser_({ isolation: true }), { sessionCaps }); | ||
|
||
assert.calledOnceWithExactly(incognitoBrowserCtx.close); | ||
assert.notCalled(defaultBrowserCtx.close); | ||
}); | ||
}); | ||
|
||
it("should call prepareBrowser on new browser", async () => { | ||
const prepareBrowser = sandbox.stub(); | ||
const browser = mkBrowser_({ prepareBrowser }); | ||
|
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.
В
sessionCaps
хранится результат ответа браузера. Т.е. он содержит реальные версии браузеров.version
использую в случае если пользователь запускает jsonwp браузер.