@wdio/devtools-script 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/script.js +5850 -0
- package/index.html +1 -0
- package/package.json +23 -0
- package/src/collector.ts +47 -0
- package/src/collectors/collector.ts +4 -0
- package/src/collectors/consoleLogs.ts +35 -0
- package/src/index.ts +66 -0
- package/src/logger.ts +13 -0
- package/src/utils.ts +110 -0
- package/tests/__snapshots__/preload.test.ts.snap +190 -0
- package/tests/preload.test.ts +47 -0
- package/tsconfig.json +7 -0
- package/types.d.ts +40 -0
- package/vite.config.ts +15 -0
package/index.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<script type="module" src="./src/index.ts"></script>
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wdio/devtools-script",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Script to be injected into a page to trace the page",
|
|
5
|
+
"author": "Christian Bromann <mail@bromann.dev>",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./types.d.ts",
|
|
8
|
+
"exports": "./dist/script.js",
|
|
9
|
+
"typeScriptVersion": "^5.0.0",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"htm": "^3.1.1",
|
|
12
|
+
"parse5": "^8.0.0",
|
|
13
|
+
"preact": "^10.27.1",
|
|
14
|
+
"vite-plugin-singlefile": "^2.3.0"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "vite",
|
|
19
|
+
"build": "tsc && vite build",
|
|
20
|
+
"preview": "vite preview",
|
|
21
|
+
"test": "eslint ."
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/collector.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getLogs, clearLogs } from './logger.js'
|
|
2
|
+
import { ConsoleLogCollector } from './collectors/consoleLogs.js'
|
|
3
|
+
|
|
4
|
+
class DataCollector {
|
|
5
|
+
#metadata = {
|
|
6
|
+
url: window.location.href,
|
|
7
|
+
viewport: window.visualViewport!
|
|
8
|
+
}
|
|
9
|
+
#errors: string[] = []
|
|
10
|
+
#mutations: TraceMutation[] = []
|
|
11
|
+
#consoleLogs = new ConsoleLogCollector()
|
|
12
|
+
|
|
13
|
+
captureError (err: Error) {
|
|
14
|
+
const error = err.stack || err.message
|
|
15
|
+
this.#errors.push(error)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
captureMutation (mutations: TraceMutation[]) {
|
|
19
|
+
this.#mutations.push(...mutations)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
reset () {
|
|
23
|
+
this.#errors = []
|
|
24
|
+
this.#mutations = []
|
|
25
|
+
this.#consoleLogs.clear()
|
|
26
|
+
clearLogs()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getMetadata () {
|
|
30
|
+
return this.#metadata
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getTraceData () {
|
|
34
|
+
const data = {
|
|
35
|
+
errors: this.#errors,
|
|
36
|
+
mutations: this.#mutations,
|
|
37
|
+
consoleLogs: this.#consoleLogs.getArtifacts(),
|
|
38
|
+
traceLogs: getLogs(),
|
|
39
|
+
metadata: this.getMetadata(),
|
|
40
|
+
} as const
|
|
41
|
+
this.reset()
|
|
42
|
+
return data
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type DataCollectorType = DataCollector
|
|
47
|
+
export const collector = window.wdioTraceCollector = new DataCollector()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Collector } from './collector.js'
|
|
2
|
+
|
|
3
|
+
const consoleMethods = ['log', 'info', 'warn', 'error'] as const
|
|
4
|
+
export interface ConsoleLogs {
|
|
5
|
+
type: 'log' | 'info' | 'warn' | 'error'
|
|
6
|
+
args: any[]
|
|
7
|
+
timestamp: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ConsoleLogCollector implements Collector<ConsoleLogs> {
|
|
11
|
+
#logs: ConsoleLogs[] = []
|
|
12
|
+
constructor () {
|
|
13
|
+
consoleMethods.forEach(this.#consolePatch.bind(this))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getArtifacts () {
|
|
17
|
+
return this.#logs
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
clear(): void {
|
|
21
|
+
this.#logs = []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#consolePatch (type: (typeof consoleMethods)[number]) {
|
|
25
|
+
const orig = console[type]
|
|
26
|
+
console[type] = (...args) => {
|
|
27
|
+
this.#logs.push({
|
|
28
|
+
timestamp: Date.now(),
|
|
29
|
+
type,
|
|
30
|
+
args
|
|
31
|
+
})
|
|
32
|
+
return orig(...args)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { waitForBody, parseFragment, parseDocument, getRef, assignRef } from './utils.js'
|
|
2
|
+
import { log } from './logger.js'
|
|
3
|
+
import { collector } from './collector.js'
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
log('waiting for body to render')
|
|
7
|
+
await waitForBody()
|
|
8
|
+
log('body rendered')
|
|
9
|
+
|
|
10
|
+
assignRef(document.documentElement)
|
|
11
|
+
log('applied wdio ref ids')
|
|
12
|
+
|
|
13
|
+
const timestamp = Date.now()
|
|
14
|
+
collector.captureMutation([{
|
|
15
|
+
type: 'childList',
|
|
16
|
+
url: document.location.href,
|
|
17
|
+
timestamp,
|
|
18
|
+
addedNodes: [parseDocument(document.documentElement)],
|
|
19
|
+
removedNodes: []
|
|
20
|
+
}])
|
|
21
|
+
log('added initial page structure')
|
|
22
|
+
|
|
23
|
+
const config = { attributes: true, childList: true, subtree: true }
|
|
24
|
+
const observer = new MutationObserver((ml) => {
|
|
25
|
+
const timestamp = Date.now()
|
|
26
|
+
const mutationList = ml.filter((m) => m.attributeName !== 'data-wdio-ref')
|
|
27
|
+
|
|
28
|
+
log(`observed ${mutationList.length} mutations`)
|
|
29
|
+
try {
|
|
30
|
+
collector.captureMutation(mutationList.map(({ target: t, addedNodes: an, removedNodes: rn, type, attributeName, attributeNamespace, previousSibling: ps, nextSibling: ns, oldValue }) => {
|
|
31
|
+
const addedNodes = Array.from(an).map((node) => {
|
|
32
|
+
assignRef(node as Element)
|
|
33
|
+
return parseFragment(node as Element)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const removedNodes = Array.from(rn).map((node) => getRef(node))
|
|
37
|
+
const target = getRef(t)
|
|
38
|
+
const previousSibling = ps ? getRef(ps) : null
|
|
39
|
+
const nextSibling = ns ? getRef(ns) : null
|
|
40
|
+
|
|
41
|
+
let attributeValue: string | undefined
|
|
42
|
+
if (type === 'attributes') {
|
|
43
|
+
attributeValue = (t as Element).getAttribute(attributeName!) || ''
|
|
44
|
+
}
|
|
45
|
+
let newTextContent: string | undefined
|
|
46
|
+
if (type === 'characterData') {
|
|
47
|
+
newTextContent = (t as Element).textContent || ''
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
log(`added mutation: ${type}`)
|
|
51
|
+
return {
|
|
52
|
+
type, attributeName, attributeNamespace, oldValue, addedNodes, target,
|
|
53
|
+
removedNodes, previousSibling, nextSibling, timestamp, attributeValue,
|
|
54
|
+
newTextContent
|
|
55
|
+
} as TraceMutation
|
|
56
|
+
}))
|
|
57
|
+
} catch (err: any) {
|
|
58
|
+
collector.captureError(err)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
observer.observe(document.body, config)
|
|
62
|
+
} catch (err: any) {
|
|
63
|
+
collector.captureError(err)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
log('Finished program')
|
package/src/logger.ts
ADDED
package/src/utils.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { parse, parseFragment as parseFragmentImport, type DefaultTreeAdapterMap } from 'parse5'
|
|
2
|
+
import { h } from 'htm/preact'
|
|
3
|
+
import type { SimplifiedVNode } from '../types.ts'
|
|
4
|
+
|
|
5
|
+
import { log } from './logger.js'
|
|
6
|
+
|
|
7
|
+
export type vFragment = DefaultTreeAdapterMap['documentFragment']
|
|
8
|
+
export type vComment = DefaultTreeAdapterMap['commentNode']
|
|
9
|
+
export type vElement = DefaultTreeAdapterMap['element']
|
|
10
|
+
export type vText = DefaultTreeAdapterMap['textNode']
|
|
11
|
+
export type vChildNode = DefaultTreeAdapterMap['childNode']
|
|
12
|
+
|
|
13
|
+
function createVNode (elem: any) {
|
|
14
|
+
const { type, props } = elem
|
|
15
|
+
return { type, props } as SimplifiedVNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseNode (fragment: vFragment | vComment | vText | vChildNode): SimplifiedVNode | string {
|
|
19
|
+
const props: Record<string, any> = {}
|
|
20
|
+
|
|
21
|
+
if (fragment.nodeName === '#comment') {
|
|
22
|
+
return (fragment as vComment).data
|
|
23
|
+
}
|
|
24
|
+
if (fragment.nodeName === '#text') {
|
|
25
|
+
return (fragment as vText).value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { childNodes, attrs, tagName } = fragment as vElement
|
|
29
|
+
for (const p of (attrs || [])) {
|
|
30
|
+
props[p.name] = p.value
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
return createVNode(h(tagName, props, ...(childNodes || []).map((cn) => parseNode(cn))) as any)
|
|
35
|
+
} catch (err: any) {
|
|
36
|
+
return createVNode(h('div', { class: 'parseNode' }, err.stack))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseDocument (node: HTMLElement) {
|
|
41
|
+
try {
|
|
42
|
+
const fragment = parse(node.outerHTML)
|
|
43
|
+
return parseNode(fragment.childNodes[0])
|
|
44
|
+
} catch (err: any) {
|
|
45
|
+
return createVNode(h('div', { class: 'parseDocument' }, err.stack))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseFragment (node: Element) {
|
|
50
|
+
try {
|
|
51
|
+
const fragment = parseFragmentImport(node.outerHTML)
|
|
52
|
+
return parseNode(fragment)
|
|
53
|
+
} catch (err: any) {
|
|
54
|
+
return createVNode(h('div', { class: 'parseFragmentWrapper' }, err.stack))
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function waitForBody () {
|
|
59
|
+
let raf = 0
|
|
60
|
+
let resolve: () => void
|
|
61
|
+
let reject: (err: Error) => void
|
|
62
|
+
const waitForPromise = new Promise<void>((res, rej) => {
|
|
63
|
+
resolve = res
|
|
64
|
+
reject = rej
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const waitForTimeout = setTimeout(
|
|
68
|
+
() => reject(new Error('Timeout waiting for body')),
|
|
69
|
+
10000
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
function run () {
|
|
73
|
+
if (!document.body) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resolve()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
raf = requestAnimationFrame(run)
|
|
81
|
+
await waitForPromise
|
|
82
|
+
cancelAnimationFrame(raf)
|
|
83
|
+
clearTimeout(waitForTimeout)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let refId = 0
|
|
87
|
+
/**
|
|
88
|
+
* assign a uid to each element so we can reference it later in the vdom
|
|
89
|
+
*/
|
|
90
|
+
export function assignRef (elem: Element) {
|
|
91
|
+
if (typeof elem.querySelectorAll !== 'function') {
|
|
92
|
+
log('assignRef: elem has no querySelectorAll', elem.nodeType || elem.nodeName || elem.textContent || Object.keys(elem))
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!elem.hasAttribute('data-wdio-ref')) {
|
|
97
|
+
elem.setAttribute('data-wdio-ref', `${++refId}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
Array.from(elem.querySelectorAll('*')).forEach(
|
|
101
|
+
(el) => { el.setAttribute('data-wdio-ref', `${++refId}`) })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getRef (elem: Node) {
|
|
105
|
+
if (!elem || !(elem as Element).getAttribute) {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
return (elem as Element).getAttribute('data-wdio-ref')
|
|
109
|
+
}
|
|
110
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`should be able serialize DOM 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"addedNodes": [
|
|
7
|
+
{
|
|
8
|
+
"props": {
|
|
9
|
+
"children": [
|
|
10
|
+
{
|
|
11
|
+
"props": {
|
|
12
|
+
"children": [
|
|
13
|
+
"
|
|
14
|
+
",
|
|
15
|
+
{
|
|
16
|
+
"props": {
|
|
17
|
+
"charset": "UTF-8",
|
|
18
|
+
"data-wdio-ref": "3",
|
|
19
|
+
},
|
|
20
|
+
"type": "meta",
|
|
21
|
+
},
|
|
22
|
+
"
|
|
23
|
+
",
|
|
24
|
+
{
|
|
25
|
+
"props": {
|
|
26
|
+
"data-wdio-ref": "4",
|
|
27
|
+
"href": "/favicon.svg",
|
|
28
|
+
"rel": "icon",
|
|
29
|
+
"type": "image/svg+xml",
|
|
30
|
+
},
|
|
31
|
+
"type": "link",
|
|
32
|
+
},
|
|
33
|
+
"
|
|
34
|
+
",
|
|
35
|
+
{
|
|
36
|
+
"props": {
|
|
37
|
+
"content": "width=device-width, initial-scale=1.0",
|
|
38
|
+
"data-wdio-ref": "5",
|
|
39
|
+
"name": "viewport",
|
|
40
|
+
},
|
|
41
|
+
"type": "meta",
|
|
42
|
+
},
|
|
43
|
+
"
|
|
44
|
+
",
|
|
45
|
+
{
|
|
46
|
+
"props": {
|
|
47
|
+
"children": "Vitest Browser Runner",
|
|
48
|
+
"data-wdio-ref": "6",
|
|
49
|
+
},
|
|
50
|
+
"type": "title",
|
|
51
|
+
},
|
|
52
|
+
"
|
|
53
|
+
",
|
|
54
|
+
{
|
|
55
|
+
"props": {
|
|
56
|
+
"children": "
|
|
57
|
+
html {
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
padding: 0;
|
|
60
|
+
margin: 0;
|
|
61
|
+
}
|
|
62
|
+
body {
|
|
63
|
+
padding: 0;
|
|
64
|
+
margin: 0;
|
|
65
|
+
}
|
|
66
|
+
#vitest-ui {
|
|
67
|
+
width: 100vw;
|
|
68
|
+
height: 100vh;
|
|
69
|
+
border: none;
|
|
70
|
+
}
|
|
71
|
+
",
|
|
72
|
+
"data-wdio-ref": "7",
|
|
73
|
+
},
|
|
74
|
+
"type": "style",
|
|
75
|
+
},
|
|
76
|
+
"
|
|
77
|
+
",
|
|
78
|
+
{
|
|
79
|
+
"props": {
|
|
80
|
+
"crossorigin": "",
|
|
81
|
+
"data-wdio-ref": "8",
|
|
82
|
+
"src": "/__vitest_browser__/index-7107c1a2.js",
|
|
83
|
+
"type": "module",
|
|
84
|
+
},
|
|
85
|
+
"type": "script",
|
|
86
|
+
},
|
|
87
|
+
"
|
|
88
|
+
",
|
|
89
|
+
],
|
|
90
|
+
"data-wdio-ref": "2",
|
|
91
|
+
},
|
|
92
|
+
"type": "head",
|
|
93
|
+
},
|
|
94
|
+
"
|
|
95
|
+
",
|
|
96
|
+
{
|
|
97
|
+
"props": {
|
|
98
|
+
"children": [
|
|
99
|
+
"
|
|
100
|
+
",
|
|
101
|
+
{
|
|
102
|
+
"props": {
|
|
103
|
+
"data-wdio-ref": "10",
|
|
104
|
+
"id": "vitest-ui",
|
|
105
|
+
"src": "/__vitest__/",
|
|
106
|
+
},
|
|
107
|
+
"type": "iframe",
|
|
108
|
+
},
|
|
109
|
+
"
|
|
110
|
+
",
|
|
111
|
+
{
|
|
112
|
+
"props": {
|
|
113
|
+
"children": "
|
|
114
|
+
const moduleCache = new Map()
|
|
115
|
+
|
|
116
|
+
// this method receives a module object or \\"import\\" promise that it resolves and keeps track of
|
|
117
|
+
// and returns a hijacked module object that can be used to mock module exports
|
|
118
|
+
function wrapModule(module) {
|
|
119
|
+
if (module instanceof Promise) {
|
|
120
|
+
moduleCache.set(module, { promise: module, evaluated: false })
|
|
121
|
+
return module
|
|
122
|
+
// TODO: add a test
|
|
123
|
+
.then(m => '__vi_inject__' in m ? m.__vi_inject__ : m)
|
|
124
|
+
.finally(() => moduleCache.delete(module))
|
|
125
|
+
}
|
|
126
|
+
return '__vi_inject__' in module ? module.__vi_inject__ : module
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function exportAll(exports, sourceModule) {
|
|
130
|
+
// #1120 when a module exports itself it causes
|
|
131
|
+
// call stack error
|
|
132
|
+
if (exports === sourceModule)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
if (Object(sourceModule) !== sourceModule || Array.isArray(sourceModule))
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
for (const key in sourceModule) {
|
|
139
|
+
if (key !== 'default') {
|
|
140
|
+
try {
|
|
141
|
+
Object.defineProperty(exports, key, {
|
|
142
|
+
enumerable: true,
|
|
143
|
+
configurable: true,
|
|
144
|
+
get: () => sourceModule[key],
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
catch (_err) { }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
window.__vi_export_all__ = exportAll
|
|
153
|
+
|
|
154
|
+
// TODO: allow easier rewriting of import.meta.env
|
|
155
|
+
window.__vi_import_meta__ = {
|
|
156
|
+
env: {},
|
|
157
|
+
url: location.href,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
window.__vi_module_cache__ = moduleCache
|
|
161
|
+
window.__vi_wrap_module__ = wrapModule
|
|
162
|
+
",
|
|
163
|
+
"data-wdio-ref": "11",
|
|
164
|
+
},
|
|
165
|
+
"type": "script",
|
|
166
|
+
},
|
|
167
|
+
"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
",
|
|
172
|
+
],
|
|
173
|
+
"data-wdio-ref": "9",
|
|
174
|
+
},
|
|
175
|
+
"type": "body",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
"data-wdio-ref": "1",
|
|
179
|
+
"lang": "en",
|
|
180
|
+
},
|
|
181
|
+
"type": "html",
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
"removedNodes": [],
|
|
185
|
+
"type": "childList",
|
|
186
|
+
},
|
|
187
|
+
]
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
exports[`should be able to properly serialize changes 1`] = `"<div id=\\"change\\" data-wdio-ref=\\"12\\">some <i data-wdio-ref=\\"13\\">real</i> change</div>"`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// import { test, expect } from 'vitest'
|
|
2
|
+
// import { h, render } from 'preact'
|
|
3
|
+
// import type { VNode as PreactVNode } from 'preact'
|
|
4
|
+
|
|
5
|
+
// function transform (node: SimplifiedVNode | string): PreactVNode<{}> | string {
|
|
6
|
+
// if (typeof node !== 'object') {
|
|
7
|
+
// return node
|
|
8
|
+
// }
|
|
9
|
+
|
|
10
|
+
// const { children, ...props } = node.props
|
|
11
|
+
// const childrenRequired = children || []
|
|
12
|
+
// const c = Array.isArray(childrenRequired) ? childrenRequired : [childrenRequired]
|
|
13
|
+
// return h(node.type as string, props, ...c.map(transform)) as PreactVNode<{}>
|
|
14
|
+
// }
|
|
15
|
+
|
|
16
|
+
// test('should be able serialize DOM', async () => {
|
|
17
|
+
// await import('../src/index.ts')
|
|
18
|
+
// expect(window.wdioCaptureErrors).toEqual([])
|
|
19
|
+
// expect(window.wdioDOMChanges.length).toBe(1)
|
|
20
|
+
// expect(window.wdioDOMChanges).toMatchSnapshot()
|
|
21
|
+
// })
|
|
22
|
+
|
|
23
|
+
// test('should be able to parse serialized DOM and render it', () => {
|
|
24
|
+
// const stage = document.createDocumentFragment()
|
|
25
|
+
// const [initial] = window.wdioDOMChanges
|
|
26
|
+
// render(transform(initial.addedNodes[0]), stage)
|
|
27
|
+
// expect(document.documentElement.outerHTML)
|
|
28
|
+
// .toBe((stage.childNodes[0] as HTMLElement).outerHTML)
|
|
29
|
+
// })
|
|
30
|
+
|
|
31
|
+
// test('should be able to properly serialize changes', async () => {
|
|
32
|
+
// const change = document.createElement('div')
|
|
33
|
+
// change.setAttribute('id', 'change')
|
|
34
|
+
// change.appendChild(document.createTextNode('some '))
|
|
35
|
+
// const bold = document.createElement('i')
|
|
36
|
+
// bold.appendChild(document.createTextNode('real'))
|
|
37
|
+
// change.appendChild(bold)
|
|
38
|
+
// change.appendChild(document.createTextNode(' change'))
|
|
39
|
+
// document.body.appendChild(change)
|
|
40
|
+
|
|
41
|
+
// await new Promise((resolve) => setTimeout(resolve, 10))
|
|
42
|
+
// expect(window.wdioDOMChanges.length).toBe(2)
|
|
43
|
+
// const [, vChange] = window.wdioDOMChanges
|
|
44
|
+
// const stage = document.createDocumentFragment()
|
|
45
|
+
// render(transform((vChange.addedNodes[0] as SimplifiedVNode).props.children as SimplifiedVNode), stage)
|
|
46
|
+
// expect((stage.childNodes[0] as HTMLElement).outerHTML).toMatchSnapshot()
|
|
47
|
+
// })
|
package/tsconfig.json
ADDED
package/types.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DataCollectorType } from './src/collector.ts'
|
|
2
|
+
import type { ConsoleLog as ConsoleLogImport } from './src/collectors/consoleLogs.ts'
|
|
3
|
+
|
|
4
|
+
export interface TraceMetadata {
|
|
5
|
+
url: string
|
|
6
|
+
viewport: VisualViewport
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SimplifiedVNode {
|
|
10
|
+
type: string
|
|
11
|
+
props: Record<string, string> & { children?: SimplifiedVNode | SimplifiedVNode[] }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
type ConsoleLogs = ConsoleLogImport
|
|
16
|
+
|
|
17
|
+
interface Element {
|
|
18
|
+
'wdio-ref': string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Window {
|
|
22
|
+
wdioTraceCollector: DataCollectorType
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TraceMutation {
|
|
26
|
+
type: MutationRecordType
|
|
27
|
+
attributeName?: string
|
|
28
|
+
attributeNamespace?: string
|
|
29
|
+
attributeValue?: string
|
|
30
|
+
newTextContent?: string
|
|
31
|
+
oldValue?: string
|
|
32
|
+
addedNodes: (string | SimplifiedVNode)[]
|
|
33
|
+
target?: string
|
|
34
|
+
removedNodes: string[]
|
|
35
|
+
previousSibling?: string
|
|
36
|
+
nextSibling?: string
|
|
37
|
+
timestamp: number
|
|
38
|
+
url?: string
|
|
39
|
+
}
|
|
40
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import { viteSingleFile } from 'vite-plugin-singlefile'
|
|
3
|
+
|
|
4
|
+
// https://vitejs.dev/config/
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [viteSingleFile()],
|
|
7
|
+
build: {
|
|
8
|
+
lib: {
|
|
9
|
+
entry: 'src/index.ts',
|
|
10
|
+
formats: ['es'],
|
|
11
|
+
fileName: 'script'
|
|
12
|
+
},
|
|
13
|
+
target: 'esnext'
|
|
14
|
+
}
|
|
15
|
+
})
|