-
Notifications
You must be signed in to change notification settings - Fork 74
fix: older browsers support in time travel and assertView #1281
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
Changes from all commits
4c4beeb
1218c41
f540130
39433d5
c114a3c
a0c7be6
cb7067c
afe7486
0c95c4b
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 |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import path from "path"; | |
| import fs from "fs"; | ||
| import makeDebug from "debug"; | ||
| import { ClientBridgeError } from "./error"; | ||
| import { WdioBrowser } from "../../types"; | ||
|
|
||
| const debug = makeDebug("testplane:client-bridge"); | ||
|
|
||
|
|
@@ -28,13 +29,27 @@ export class ClientBridge<T extends Record<string, (...args: any[]) => any>> { | |
| const scriptFileName = needsCompatLib ? "bundle.compat.js" : "bundle.native.js"; | ||
| const scriptFilePath = path.join(__dirname, "..", "client-scripts", namespace, "build", scriptFileName); | ||
|
|
||
| let debugBrowserId = ""; | ||
| if (debug.enabled) { | ||
| debugBrowserId = `${(browser as WdioBrowser)?.capabilities?.browserName} ${ | ||
| (browser as WdioBrowser)?.capabilities?.browserVersion | ||
| }:${(browser as WdioBrowser)?.sessionId}`; | ||
| } | ||
|
|
||
| if (bundlesCache[scriptFilePath]) { | ||
| debug( | ||
|
Member
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. Why would we need to know it?
Member
Author
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. It actually is very useful, for example it highlights which version of the script — native or compat was chosen, which instantly makes it clear if calibration works as expected, etc. |
||
| `creating ClientBridge with cached script for namespace ${namespace} at ${scriptFilePath} for browser ${debugBrowserId}`, | ||
| ); | ||
| return new ClientBridge(browser, bundlesCache[scriptFilePath], namespace); | ||
| } | ||
|
|
||
| const bundle = await fs.promises.readFile(scriptFilePath, { encoding: "utf8" }); | ||
| bundlesCache[scriptFilePath] = bundle; | ||
|
|
||
| debug( | ||
| `creating ClientBridge with new script for namespace ${namespace} at ${scriptFilePath} for browser ${debugBrowserId}`, | ||
| ); | ||
|
|
||
| return new this(browser, bundle, namespace); | ||
| } | ||
|
|
||
|
|
@@ -89,10 +104,7 @@ export class ClientBridge<T extends Record<string, (...args: any[]) => any>> { | |
| } | ||
|
|
||
| private async _inject(): Promise<void> { | ||
| debug(` > injecting script into namespace ${this._namespace}`); | ||
| if (debug.enabled) { | ||
| console.log(this._script); | ||
| } | ||
| debug(` > injecting script into namespace ${this._namespace}: ${this._script.slice(0, 256)}...`); | ||
|
shadowusr marked this conversation as resolved.
|
||
| await this._browser.execute(this._script, this._namespace); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,26 @@ | ||
| import fs from "fs"; | ||
| import path from "path"; | ||
| import { eventWithTime } from "@rrweb/types"; | ||
| import makeDebug from "debug"; | ||
| import type { Callstack } from "./callstack"; | ||
| import { MasterEvents } from "../../events"; | ||
| import type { SnapshotsData, Test, TestContext } from "../../types"; | ||
| import { runWithoutHistory } from "./index"; | ||
| import path from "path"; | ||
|
|
||
| const debug = makeDebug("testplane:time-travel:rrweb"); | ||
|
|
||
| // Built from branch https://github.com/gemini-testing/rrweb/tree/TESTPLANE-712.syntax_err | ||
| // PR: https://github.com/rrweb-io/rrweb/pull/1735 | ||
| // Issue: https://github.com/rrweb-io/rrweb/issues/1734 | ||
| const rrwebCode = fs.readFileSync(path.join(__dirname, "../client-scripts/rrweb-record.min.js"), "utf-8"); | ||
| const sessionsWithRrwebRequested = new WeakSet<WebdriverIO.Browser>(); | ||
| const sessionsWithRrwebSent = new WeakSet<WebdriverIO.Browser>(); | ||
| const sessionsWithUnsupportedRrweb = new WeakSet<WebdriverIO.Browser>(); | ||
|
|
||
| interface CollectRrwebEventsResult { | ||
| isRrwebSupported?: false; | ||
| isRrwebInstalled: boolean; | ||
| rrwebEvents: eventWithTime[]; | ||
| evalError?: string; | ||
| } | ||
|
|
||
| /* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
|
|
@@ -23,19 +29,47 @@ export async function installRrwebAndCollectEvents( | |
| callstack: Callstack, | ||
| ): Promise<eventWithTime[]> { | ||
| return runWithoutHistory<Promise<eventWithTime[]>>({ callstack }, async () => { | ||
| const shouldSendRrwebCode = !sessionsWithRrwebRequested.has(session); | ||
| if (sessionsWithUnsupportedRrweb.has(session)) { | ||
| return []; | ||
| } | ||
|
|
||
| const shouldSendRrwebCode = !sessionsWithRrwebSent.has(session); | ||
|
|
||
| if (shouldSendRrwebCode) { | ||
| sessionsWithRrwebRequested.add(session); | ||
| sessionsWithRrwebSent.add(session); | ||
| } | ||
|
|
||
| const result = await collectRrwebEvents(session, shouldSendRrwebCode ? rrwebCode : null); | ||
| let result = await collectRrwebEvents(session, shouldSendRrwebCode ? rrwebCode : undefined); | ||
|
|
||
| let debugBrowserId = ""; | ||
| if (debug.enabled) { | ||
| debugBrowserId = `${session?.capabilities?.browserName} ${session?.capabilities?.browserVersion}:${session?.sessionId}`; | ||
| } | ||
|
|
||
| if (result.isRrwebSupported === false) { | ||
| debug("rrweb is not supported in browser %s, error: %s", debugBrowserId, result.evalError); | ||
| sessionsWithUnsupportedRrweb.add(session); | ||
|
shadowusr marked this conversation as resolved.
|
||
|
|
||
| return []; | ||
| } | ||
|
|
||
| // If rrweb is installed (success) or we already provided the code (no point in trying again) | ||
| if (result.isRrwebInstalled || shouldSendRrwebCode) { | ||
| return result.rrwebEvents; | ||
| } | ||
|
|
||
| return (await collectRrwebEvents(session, rrwebCode)).rrwebEvents; | ||
| debug("collectRrwebEvents, rrweb was not installed, sending code again"); | ||
|
|
||
| result = await collectRrwebEvents(session, rrwebCode); | ||
|
|
||
| if (result.isRrwebSupported === false) { | ||
| debug("rrweb is not supported in browser %s, error: %s", debugBrowserId, result.evalError); | ||
| sessionsWithUnsupportedRrweb.add(session); | ||
|
|
||
| return []; | ||
| } | ||
|
|
||
| return result.rrwebEvents; | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -46,9 +80,9 @@ export async function cleanupRrweb(session: WebdriverIO.Browser, callstack: Call | |
| try { | ||
| // @ts-expect-error | ||
| const rrwebEvents = window.rrwebEvents; | ||
| const rrwebData = rrwebEvents?.testplane; | ||
| const rrwebData = rrwebEvents && rrwebEvents.testplane; | ||
|
|
||
| if (rrwebData?.stopRecording) { | ||
| if (rrwebData && rrwebData.stopRecording) { | ||
| try { | ||
| rrwebData.stopRecording(); | ||
| } catch (e) { | ||
|
|
@@ -57,8 +91,8 @@ export async function cleanupRrweb(session: WebdriverIO.Browser, callstack: Call | |
| } | ||
|
|
||
| try { | ||
| const colorSchemeMedia = rrwebData?.colorSchemeMedia; | ||
| const colorSchemeListener = rrwebData?.colorSchemeListener; | ||
| const colorSchemeMedia = rrwebData && rrwebData.colorSchemeMedia; | ||
| const colorSchemeListener = rrwebData && rrwebData.colorSchemeListener; | ||
|
Comment on lines
+94
to
+95
Member
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. Whats the difference?
Member
Author
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. optional chaining is supported from chrome 80: https://caniuse.com/mdn-javascript_operators_optional_chaining While we are targeting much earlier versions. |
||
|
|
||
| if (colorSchemeMedia && colorSchemeListener) { | ||
| colorSchemeMedia.removeEventListener("change", colorSchemeListener); | ||
|
|
@@ -67,7 +101,7 @@ export async function cleanupRrweb(session: WebdriverIO.Browser, callstack: Call | |
| /**/ | ||
| } | ||
|
|
||
| if (rrwebData?.isInstalledByTestplane) { | ||
| if (rrwebData && rrwebData.isInstalledByTestplane) { | ||
| // @ts-expect-error | ||
| delete window.rrweb; | ||
|
|
||
|
|
@@ -86,21 +120,22 @@ export async function cleanupRrweb(session: WebdriverIO.Browser, callstack: Call | |
| } catch (e) { | ||
| /**/ | ||
| } finally { | ||
| sessionsWithRrwebRequested.delete(session); | ||
| sessionsWithRrwebSent.delete(session); | ||
| sessionsWithUnsupportedRrweb.delete(session); | ||
| } | ||
| } | ||
|
|
||
| function collectRrwebEvents( | ||
| session: WebdriverIO.Browser, | ||
| rrwebRecordFnCode: string | null, | ||
| rrwebRecordFnCode: string | undefined, | ||
|
Member
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. nit: ->
Member
Author
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. Well it's actually important here. The story is that in older versions, argument is expected to be string or undefined precisely (with null it throws an error). However, we don't mean that "argument can be omitted". We mean precisely that it must be passed, but can be either string or undefined. |
||
| ): Promise<CollectRrwebEventsResult> { | ||
| return session.execute( | ||
| (rrwebRecordFnCode, serverTime) => { | ||
| const isRrwebInstalled = (): boolean => { | ||
| try { | ||
| // @ts-expect-error | ||
| return Boolean(window.rrweb); | ||
| } catch { | ||
| } catch (e) { | ||
|
Member
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. ?
Member
Author
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. The |
||
| return false; | ||
| } | ||
| }; | ||
|
|
@@ -112,7 +147,7 @@ function collectRrwebEvents( | |
| result = window.rrwebEvents.slice(window.lastProcessedRrwebEvent + 1); | ||
| // @ts-expect-error | ||
| window.lastProcessedRrwebEvent = window.rrwebEvents.length - 1; | ||
| } catch { | ||
| } catch (e) { | ||
| result = []; | ||
| } | ||
|
|
||
|
|
@@ -159,7 +194,17 @@ function collectRrwebEvents( | |
|
|
||
| try { | ||
| if (!isRrwebInstalled() && rrwebRecordFnCode) { | ||
| window.eval(rrwebRecordFnCode); | ||
| try { | ||
| window.eval(rrwebRecordFnCode); | ||
| } catch (e) { | ||
| return { | ||
| isRrwebSupported: false, | ||
| isRrwebInstalled: false, | ||
| rrwebEvents: [], | ||
| evalError: String(e), | ||
| }; | ||
| } | ||
|
|
||
| // @ts-expect-error | ||
| window.lastProcessedRrwebEvent = -1; | ||
| // @ts-expect-error | ||
|
|
||
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.
этого точно хватит для id? у нас же может быть 2 браузера одинаковой версии, но с разными капабилити (desktp/touch)
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.
Added sessionId