Skip to content

Commit d353070

Browse files
scale browser correctly
1 parent 97a2b56 commit d353070

File tree

11 files changed

+122
-52
lines changed

11 files changed

+122
-52
lines changed

packages/app/src/components/browser/snapshot.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export class DevtoolsBrowser extends Element {
3535
width: 100%;
3636
height: 100%;
3737
display: flex;
38-
padding: 2rem;
38+
margin: 2rem;
39+
align-items: center;
40+
justify-content: center;
3941
}
4042
4143
.frame-dot {
@@ -58,6 +60,7 @@ export class DevtoolsBrowser extends Element {
5860

5961
async connectedCallback() {
6062
super.connectedCallback()
63+
window.addEventListener('resize', this.#setIframeSize.bind(this))
6164
window.addEventListener('window-drag', this.#setIframeSize.bind(this))
6265
window.addEventListener('app-mutation-highlight', this.#highlightMutation.bind(this))
6366
await this.updateComplete
@@ -70,16 +73,36 @@ export class DevtoolsBrowser extends Element {
7073
return
7174
}
7275

76+
this.section.style.width = 'auto'
77+
this.section.style.height = 'auto'
78+
7379
this.iframe.removeAttribute('style')
74-
const frameSize = this.section.getBoundingClientRect()
80+
const viewportWidth = this.data.metadata.viewport.width
81+
const viewportHeight = this.data.metadata.viewport.height
82+
const frameSize = this.getBoundingClientRect()
7583
const headerSize = this.header.getBoundingClientRect()
76-
this.iframe.style.width = `${frameSize.width}px`
77-
this.iframe.style.height = `${frameSize.height - headerSize.height}px`
84+
85+
let scale = frameSize.width / viewportWidth
86+
if (scale > 0.85) {
87+
/**
88+
* Make sure we stop scaling at 85% of the viewport width to ensure
89+
* we keep the aspect ratio. We substract 0.05 to have a bit of a
90+
* padding
91+
*/
92+
scale = (frameSize.height / viewportHeight) - 0.05
93+
}
94+
95+
this.section.style.width = `${viewportWidth * scale}px`
96+
this.section.style.height = `${Math.min(frameSize.height, viewportHeight * scale)}px`
97+
this.iframe.style.width = `${viewportWidth}px`
98+
// this.iframe.style.height = `${(Math.min(frameSize.height, viewportHeight * scale) - headerSize.height)}px`
99+
this.iframe.style.height = `${viewportHeight - (headerSize.height / scale)}px`
100+
this.iframe.style.transform = `scale(${scale})`
78101
}
79102

80-
async #renderNewDocument (doc: Document) {
103+
async #renderNewDocument (doc: SimplifiedVNode) {
81104
const root = transform(doc)
82-
const baseTag = h('base', { href: 'https://selenium.dev' })
105+
const baseTag = h('base', { href: this.data.metadata.url })
83106
const head: VNode<{}> | undefined = (root.props.children as VNode[])
84107
.filter(Boolean)
85108
.find((node) => node!.type === 'head')
@@ -102,24 +125,32 @@ export class DevtoolsBrowser extends Element {
102125
if (!docEl) {
103126
return
104127
}
128+
129+
/**
130+
* remove script tags from application as we are only interested in the static
131+
* representation of the page
132+
*/
133+
[...this.#vdom.querySelectorAll('script')].forEach((el) => el.remove())
134+
105135
docEl.ownerDocument.replaceChild(this.#vdom, docEl)
106136
}
107137

108-
async #handleMutation (mutation: MutationRecord) {
138+
async #handleMutation (mutation: TraceMutation) {
109139
if (!this.iframe) {
110140
await this.updateComplete
111141
}
112142

113143
const hasRenderedFrame = this.iframe?.contentDocument?.documentElement
114144
.querySelectorAll('*').length === 2 // only body and head are in an empty iframe
115-
if (hasRenderedFrame) {
116-
return this.#renderNewDocument(mutation.addedNodes[0] as Document)
145+
const doc = mutation.addedNodes[0]
146+
if (hasRenderedFrame && typeof doc !== 'string') {
147+
return this.#renderNewDocument(doc)
117148
}
118149

119150
// TODO: handle mutations
120151
}
121152

122-
#highlightMutation (ev: CustomEvent<MutationRecord>) {
153+
#highlightMutation (ev: CustomEvent<TraceMutation>) {
123154
const mutation = ev.detail
124155
const docEl = this.iframe?.contentDocument
125156
if (!docEl) {
@@ -153,7 +184,7 @@ export class DevtoolsBrowser extends Element {
153184
${this.data.metadata.url}
154185
</div>
155186
</header>
156-
<iframe></iframe>
187+
<iframe class="origin-top-left"></iframe>
157188
</section>
158189
`
159190
}

packages/app/src/components/header.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class DevtoolsHeader extends Element {
3939
<icon-custom-logo class="p-2 dark:p-2 h-full"></icon-custom-logo>
4040
<h1 class="font-bold text-white">WebdriverIO Devtools</h1>
4141
<nav class="ml-auto mr-2">
42+
<button>Docs</button>
4243
<button class="p-2" @click="${this.#switchMode}">
4344
<icon-mdi-moon-waning-crescent class="${this.#darkMode ? 'hidden' : 'show'}"></icon-mdi-moon-waning-crescent>
4445
<icon-mdi-white-balance-sunny class="${this.#darkMode ? 'show' : 'hidden'}"></icon-mdi-white-balance-sunny>

packages/app/src/components/workbench/actions.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Element } from '@core/element'
22
import { html, css } from 'lit'
33
import { customElement } from 'lit/decorators.js'
44
import { consume } from '@lit/context'
5+
import type { CommandLog } from '@devtools/hook/types'
56

67
import { context, type TraceLog } from '../../context.js'
78

@@ -15,8 +16,8 @@ const ICON_CLASS = 'w-[20px] h-[20px] m-1 mr-2 shrink-0'
1516
const SOURCE_COMPONENT = 'wdio-devtools-actions'
1617
@customElement(SOURCE_COMPONENT)
1718
export class DevtoolsActions extends Element {
18-
#mutations: MutationRecord[] = []
19-
#activeMutation?: number
19+
#entries: (TraceMutation | CommandLog)[] = []
20+
#activeEntry?: number
2021
#highlightedMutation?: number
2122

2223
static styles = [...Element.styles, css`
@@ -32,28 +33,35 @@ export class DevtoolsActions extends Element {
3233

3334
connectedCallback(): void {
3435
super.connectedCallback()
35-
this.#mutations = this.data.mutations
36+
this.#entries = [...this.data.mutations, ...this.data.commands]
37+
.sort((a, b) => a.timestamp - b.timestamp)
3638
}
3739

3840
render() {
39-
if (!this.#mutations.length) {
41+
if (!this.#entries.length) {
4042
return html`<section class="flex items-center justify-center text-sm w-full h-full">No events logged!</section>`
4143
}
4244

43-
return this.#mutations.map((mutation, i) => {
45+
return this.#entries.map((entry, i) => {
46+
if ('command' in entry) {
47+
return html`
48+
<button>${entry.command}</button>
49+
`
50+
}
51+
4452
return html`
4553
<button
4654
@mousemove="${() => this.#showMutationTarget(i)}"
4755
@click="${() => this.#selectMutation(i)}"
48-
class="flex items-center justify-center text-sm w-full px-4 hover:bg-toolbarHoverBackground ${this.#activeMutation === i ? 'bg-toolbarHoverBackground' : ''}"
56+
class="flex items-center justify-center text-sm w-full px-4 hover:bg-toolbarHoverBackground ${this.#activeEntry === i ? 'bg-toolbarHoverBackground' : ''}"
4957
>
50-
${this.#getMutationLabel(mutation)}
58+
${this.#getMutationLabel(entry)}
5159
</button>
5260
`
5361
})
5462
}
5563

56-
#getMutationLabel(mutation: MutationRecord) {
64+
#getMutationLabel(mutation: TraceMutation) {
5765
if (mutation.type === 'attributes') {
5866
return this.#getAttributeMutationLabel(mutation)
5967
} else if (mutation.type === 'childList') {
@@ -62,14 +70,14 @@ export class DevtoolsActions extends Element {
6270
return 'Unknown mutation'
6371
}
6472

65-
#getAttributeMutationLabel(mutation: MutationRecord) {
73+
#getAttributeMutationLabel(mutation: TraceMutation) {
6674
return html`
6775
<icon-mdi-pencil class="${ICON_CLASS}"></icon-mdi-pencil>
6876
<span class="flex-grow">${mutation.target} attribute "<code>${mutation.attributeName}</code>" changed</span>
6977
`
7078
}
7179

72-
#getChildListMutationLabel(mutation: MutationRecord) {
80+
#getChildListMutationLabel(mutation: TraceMutation) {
7381
if (mutation.addedNodes.length === 1 && (mutation.addedNodes[0] as any).type === 'html') {
7482
return html`
7583
<icon-mdi-document class="${ICON_CLASS}"></icon-mdi-document>
@@ -83,9 +91,9 @@ export class DevtoolsActions extends Element {
8391
}
8492

8593
#selectMutation(i: number) {
86-
this.#activeMutation = i
94+
this.#activeEntry = i
8795
const event = new CustomEvent('app-mutation-select', {
88-
detail: this.#mutations[this.#activeMutation]
96+
detail: this.#entries[this.#activeEntry]
8997
})
9098
window.dispatchEvent(event)
9199
this.requestUpdate()
@@ -97,7 +105,7 @@ export class DevtoolsActions extends Element {
97105
}
98106
this.#highlightedMutation = i
99107
const event = new CustomEvent('app-mutation-highlight', {
100-
detail: this.#mutations[i]
108+
detail: this.#entries[i]
101109
})
102110
window.dispatchEvent(event)
103111
}

packages/app/src/components/workbench/list.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@ export class DevtoolsList extends Element {
5151
${this.#renderSectionHeader(this.label)}
5252
<dl class="flex flex-wrap transition-all ${this.isCollapsed ? 'mt-0' : 'mt-2'}">
5353
${entries.map(([key, val], i) => {
54-
const className = `transition-[max-height] border-b-panelBorder overflow-y-hidden ${i === (entries.length - 1) && !this.isCollapsed ? 'border-b-[1px]' : ''} ${this.isCollapsed ? 'max-h-0' : 'max-h-[100px]'} basis-2/4`
54+
let className = 'basis-2/4 transition-all border-b-panelBorder overflow-y-hidden'
55+
if (i === (entries.length - 1)) {
56+
className += this.isCollapsed ? ' pb-0' : ' pb-2'
57+
if (!this.isCollapsed) {
58+
className += ' border-b-[1px]'
59+
}
60+
}
61+
className += this.isCollapsed ? ' max-h-0' : ' max-h-[500px]'
5562
return html`
5663
<dt class="${className} font-bold px-2">${key}</dt>
5764
<dd class="${className}">${this.#renderMetadataProp(val)}</dd>

packages/app/src/vite-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="vite/client" />
22

33
interface GlobalEventHandlersEventMap {
4-
'app-mutation-highlight': CustomEvent<MutationRecord>
4+
'app-mutation-highlight': CustomEvent<TraceMutation>
55
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
66
'app-test-filter': CustomEvent<import('./components/sidebar/filter').DevtoolsSidebarFilter>
77
'app-logs': CustomEvent<string>

packages/hook/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ async function injectScript (browser: WebdriverIO.Browser) {
7676
}
7777

7878
async function captureTrace (browser: WebdriverIO.Browser, command: (keyof WebDriverCommands), args: any, result?: any, error?: Error) {
79+
const timestamp = Date.now()
80+
7981
/**
8082
* only capture trace if script was injected and command is a page transition command
8183
*/
@@ -93,7 +95,7 @@ async function captureTrace (browser: WebdriverIO.Browser, command: (keyof WebDr
9395
commandsLog = []
9496
}
9597

96-
commandsLog.push({ command, args, result, error })
98+
commandsLog.push({ command, args, result, error, timestamp })
9799
const outputDir = browser.options.outputDir || process.cwd()
98100
const { capabilities, ...options } = browser.options as Options.WebdriverIO
99101
const traceLog: TraceLog = {

packages/hook/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface CommandLog {
66
args: any[]
77
result: any
88
error?: Error
9+
timestamp: number
910
}
1011

1112
export enum TraceType {
@@ -14,14 +15,15 @@ export enum TraceType {
1415
}
1516

1617
export interface TraceLog {
17-
mutations: MutationRecord[]
18+
mutations: TraceMutation[]
1819
logs: string[]
1920
metadata: {
2021
type: TraceType
2122
id: string
2223
url: string
2324
options: Omit<Options.WebdriverIO, 'capabilities'>
2425
capabilities: Capabilities.RemoteCapability
26+
viewport: VisualViewport
2527
}
2628
commands: CommandLog[]
2729
}

packages/script/src/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ window.wdioCaptureErrors = []
44
window.wdioDOMChanges = []
55
window.wdioMetadata = {
66
url: window.location.href,
7-
id: `wdio-trace-${Math.random().toString().slice(2)}`
7+
id: `wdio-trace-${Math.random().toString().slice(2)}`,
8+
viewport: window.visualViewport!
89
}
910

1011
try {
@@ -15,15 +16,18 @@ try {
1516
assignRef(document.documentElement)
1617
log('applied wdio ref ids')
1718

19+
const timestamp = Date.now()
1820
window.wdioDOMChanges.push({
1921
type: 'childList',
22+
timestamp,
2023
addedNodes: [parseDocument(document.documentElement)],
2124
removedNodes: []
2225
})
2326
log('added initial page structure')
2427

2528
const config = { attributes: true, childList: true, subtree: true }
2629
const observer = new MutationObserver((ml) => {
30+
const timestamp = Date.now()
2731
const mutationList = ml.filter((m) => m.attributeName !== 'data-wdio-ref')
2832

2933
log(`observed ${mutationList.length} mutations`)
@@ -40,7 +44,10 @@ try {
4044
const nextSibling = ns ? getRef(ns) : null
4145

4246
log(`added mutation: ${type}`)
43-
return { type, attributeName, attributeNamespace, oldValue, addedNodes, target, removedNodes, previousSibling, nextSibling }
47+
return {
48+
type, attributeName, attributeNamespace, oldValue, addedNodes, target,
49+
removedNodes, previousSibling, nextSibling, timestamp
50+
} as TraceMutation
4451
}))
4552
} catch (err: any) {
4653
window.wdioCaptureErrors.push(err.stack)

packages/script/src/utils.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { parse, parseFragment as parseFragmentImport, type DefaultTreeAdapterMap } from 'parse5'
22
import { h } from 'htm/preact'
3-
import type { VNode } from 'preact'
43

5-
type vFragment = DefaultTreeAdapterMap['documentFragment']
6-
type vComment = DefaultTreeAdapterMap['commentNode']
7-
type vElement = DefaultTreeAdapterMap['element']
8-
type vText = DefaultTreeAdapterMap['textNode']
9-
type vChildNode = DefaultTreeAdapterMap['childNode']
4+
export type vFragment = DefaultTreeAdapterMap['documentFragment']
5+
export type vComment = DefaultTreeAdapterMap['commentNode']
6+
export type vElement = DefaultTreeAdapterMap['element']
7+
export type vText = DefaultTreeAdapterMap['textNode']
8+
export type vChildNode = DefaultTreeAdapterMap['childNode']
109

11-
function createVNode (elem: VNode<any>) {
10+
function createVNode (elem: any) {
1211
const { type, props } = elem
13-
return { type, props }
12+
return { type, props } as SimplifiedVNode
1413
}
1514

16-
export function parseNode (fragment: vFragment | vComment | vText | vChildNode): any {
15+
export function parseNode (fragment: vFragment | vComment | vText | vChildNode): SimplifiedVNode | string {
1716
const props: Record<string, any> = {}
1817

1918
if (fragment.nodeName === '#comment') {

packages/script/tests/preload.test.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
11
import { test, expect } from 'vitest'
22
import { h, render } from 'preact'
3-
import type { VNode } from 'preact'
3+
import type { VNode as PreactVNode } from 'preact'
44

5-
interface SanitizedVNode {
6-
type: string
7-
props: Record<string, any> & {
8-
children?: SanitizedVNode | SanitizedVNode[]
9-
}
10-
}
11-
12-
function transform (node: SanitizedVNode): VNode<{}> {
5+
function transform (node: SimplifiedVNode | string): PreactVNode<{}> | string {
136
if (typeof node !== 'object') {
14-
return node as VNode<{}>
7+
return node
158
}
169

1710
const { children, ...props } = node.props
1811
const childrenRequired = children || []
1912
const c = Array.isArray(childrenRequired) ? childrenRequired : [childrenRequired]
20-
return h(node.type as string, props, ...c.map(transform)) as VNode<{}>
13+
return h(node.type as string, props, ...c.map(transform)) as PreactVNode<{}>
2114
}
2215

2316
test('should be able serialize DOM', async () => {
@@ -49,6 +42,6 @@ test('should be able to properly serialize changes', async () => {
4942
expect(window.wdioDOMChanges.length).toBe(2)
5043
const [, vChange] = window.wdioDOMChanges
5144
const stage = document.createDocumentFragment()
52-
render(transform(vChange.addedNodes[0].props.children), stage)
45+
render(transform((vChange.addedNodes[0] as SimplifiedVNode).props.children as SimplifiedVNode), stage)
5346
expect((stage.childNodes[0] as HTMLElement).outerHTML).toMatchSnapshot()
5447
})

0 commit comments

Comments
 (0)