diff --git a/eslint.config.cjs b/eslint.config.cjs index 0119fee..f037455 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -77,7 +77,7 @@ module.exports = [ rules: { '@typescript-eslint/no-unused-vars': [ 'error', - { argsIgnorePattern: '^_' } + { argsIgnorePattern: '^_', ignoreRestSiblings: true } ], '@typescript-eslint/consistent-type-imports': 'error', 'no-unused-vars': 'off', diff --git a/example/wdio.conf.ts b/example/wdio.conf.ts index 7401cae..3cbc907 100644 --- a/example/wdio.conf.ts +++ b/example/wdio.conf.ts @@ -63,13 +63,13 @@ export const config: Options.Testrunner = { capabilities: [ { browserName: 'chrome', - browserVersion: '144.0.7559.60', // specify chromium browser version for testing + browserVersion: '146.0.7680.72', // specify chromium browser version for testing 'goog:chromeOptions': { args: [ '--headless', '--disable-gpu', '--remote-allow-origins=*', - '--window-size=1280,800' + '--window-size=1600,1200' ] } // }, { diff --git a/package.json b/package.json index 20ae232..1cf55c4 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "scripts": { "build": "pnpm --parallel build", "demo": "wdio run ./example/wdio.conf.ts", + "demo:nightwatch": "pnpm --filter @wdio/nightwatch-devtools example", "dev": "pnpm --parallel dev", "preview": "pnpm --parallel preview", "test": "vitest run", @@ -17,7 +18,10 @@ "pnpm": { "overrides": { "vite": "^7.3.0" - } + }, + "ignoredBuiltDependencies": [ + "chromedriver" + ] }, "devDependencies": { "@types/node": "^25.0.3", diff --git a/packages/app/src/app.ts b/packages/app/src/app.ts index 493bb8c..1f1c190 100644 --- a/packages/app/src/app.ts +++ b/packages/app/src/app.ts @@ -6,14 +6,13 @@ import { TraceType, type TraceLog } from '@wdio/devtools-service/types' import { Element } from '@core/element' import { DataManagerController } from './controller/DataManager.js' import { DragController, Direction } from './utils/DragController.js' +import { SIDEBAR_MIN_WIDTH } from './controller/constants.js' import './components/header.js' import './components/sidebar.js' import './components/workbench.js' import './components/onboarding/start.js' -const SIDEBAR_MIN_WIDTH = 250 - @customElement('wdio-devtools') export class WebdriverIODevtoolsApplication extends Element { dataManager = new DataManagerController(this) @@ -71,8 +70,12 @@ export class WebdriverIODevtoolsApplication extends Element { this.requestUpdate() } - #clearExecutionData({ detail }: { detail?: { uid?: string } }) { - this.dataManager.clearExecutionData(detail?.uid) + #clearExecutionData({ + detail + }: { + detail?: { uid?: string; entryType?: 'suite' | 'test' } + }) { + this.dataManager.clearExecutionData(detail?.uid, detail?.entryType) } #mainContent() { diff --git a/packages/app/src/components/browser/snapshot.ts b/packages/app/src/components/browser/snapshot.ts index d852f1a..436de78 100644 --- a/packages/app/src/components/browser/snapshot.ts +++ b/packages/app/src/components/browser/snapshot.ts @@ -9,10 +9,10 @@ import type { CommandLog } from '@wdio/devtools-service/types' import { mutationContext, - type TraceMutation, metadataContext, - type Metadata -} from '../../controller/DataManager.js' + commandContext +} from '../../controller/context.js' +import type { Metadata } from '@wdio/devtools-service/types' import '~icons/mdi/world.js' import '../placeholder.js' @@ -20,15 +20,16 @@ import '../placeholder.js' const MUTATION_SELECTOR = '__mutation-highlight__' function transform(node: any): VNode<{}> { - if (typeof node !== 'object') { + if (typeof node !== 'object' || node === null) { + // Plain string/number text node — return as-is for Preact to render as text. return node as VNode<{}> } - const { children, ...props } = node.props + const { children, ...props } = node.props ?? {} /** * ToDo(Christian): fix way we collect data on added nodes in script */ - if (!node.type && children.type) { + if (!node.type && children?.type) { return transform(children) } @@ -44,6 +45,8 @@ const COMPONENT = 'wdio-devtools-browser' export class DevtoolsBrowser extends Element { #vdom = document.createDocumentFragment() #activeUrl?: string + /** Base64 PNG of the screenshot for the currently selected command, or null. */ + #screenshotData: string | null = null @consume({ context: metadataContext, subscribe: true }) metadata: Metadata | undefined = undefined @@ -51,6 +54,9 @@ export class DevtoolsBrowser extends Element { @consume({ context: mutationContext, subscribe: true }) mutations: TraceMutation[] = [] + @consume({ context: commandContext, subscribe: true }) + commands: CommandLog[] = [] + static styles = [ ...Element.styles, css` @@ -112,6 +118,31 @@ export class DevtoolsBrowser extends Element { border-radius: 0 0 0.5rem 0.5rem; min-height: 0; } + + .screenshot-overlay { + position: absolute; + inset: 0; + background: #111; + display: flex; + align-items: flex-start; + justify-content: center; + border-radius: 0 0 0.5rem 0.5rem; + overflow: hidden; + } + + .screenshot-overlay img { + max-width: 100%; + height: auto; + display: block; + } + + .iframe-wrapper { + position: relative; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + } ` ] @@ -148,9 +179,16 @@ export class DevtoolsBrowser extends Element { return } + // viewport may not be serialized yet (race between metadata message and + // first resize event), or may arrive without dimensions — fall back to + // sensible defaults so we never throw. + const viewportWidth = (metadata.viewport as any)?.width || 1280 + const viewportHeight = (metadata.viewport as any)?.height || 800 + if (!viewportWidth || !viewportHeight) { + return + } + this.iframe.removeAttribute('style') - const viewportWidth = metadata.viewport.width - const viewportHeight = metadata.viewport.height const frameSize = this.getBoundingClientRect() const headerSize = this.header.getBoundingClientRect() @@ -178,23 +216,8 @@ export class DevtoolsBrowser extends Element { ) async #renderCommandScreenshot(command?: CommandLog) { - const screenshot = command?.screenshot - if (!screenshot) { - return - } - - if (!this.iframe) { - await this.updateComplete - } - if (!this.iframe) { - return - } - - this.iframe.srcdoc = ` -
-