Skip to content

Commit 2d62051

Browse files
authoredJul 15, 2024··
feat(browser): allow preview and open in the editor screenshot error from ui (#6113)
1 parent 3826941 commit 2d62051

File tree

4 files changed

+161
-4
lines changed

4 files changed

+161
-4
lines changed
 

‎packages/browser/src/node/plugin.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { fileURLToPath } from 'node:url'
22
import { createRequire } from 'node:module'
3-
import { readFileSync } from 'node:fs'
4-
import { basename, resolve } from 'pathe'
3+
import { lstatSync, readFileSync } from 'node:fs'
4+
import type { Stats } from 'node:fs'
5+
import { basename, extname, resolve } from 'pathe'
56
import sirv from 'sirv'
67
import type { WorkspaceProject } from 'vitest/node'
78
import { getFilePoolName, resolveApiServerConfig, resolveFsAllow, distDir as vitestDist } from 'vitest/node'
@@ -100,6 +101,52 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
100101
},
101102
}),
102103
)
104+
105+
const screenshotFailures = project.config.browser.ui && project.config.browser.screenshotFailures
106+
107+
// eslint-disable-next-line prefer-arrow-callback
108+
screenshotFailures && server.middlewares.use(`${base}__screenshot-error`, function vitestBrowserScreenshotError(req, res) {
109+
if (!req.url || !browserServer.provider) {
110+
res.statusCode = 404
111+
res.end()
112+
return
113+
}
114+
115+
const url = new URL(req.url, 'http://localhost')
116+
const file = url.searchParams.get('file')
117+
if (!file) {
118+
res.statusCode = 404
119+
res.end()
120+
return
121+
}
122+
123+
let stat: Stats | undefined
124+
try {
125+
stat = lstatSync(file)
126+
}
127+
catch (_) {
128+
}
129+
130+
if (!stat?.isFile()) {
131+
res.statusCode = 404
132+
res.end()
133+
return
134+
}
135+
136+
const ext = extname(file)
137+
const buffer = readFileSync(file)
138+
res.setHeader(
139+
'Cache-Control',
140+
'public,max-age=0,must-revalidate',
141+
)
142+
res.setHeader('Content-Length', buffer.length)
143+
res.setHeader('Content-Type', ext === 'jpeg' || ext === 'jpg'
144+
? 'image/jpeg'
145+
: ext === 'webp'
146+
? 'image/webp'
147+
: 'image/png')
148+
res.end(buffer)
149+
})
103150
},
104151
},
105152
{

‎packages/ui/client/components.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ declare module 'vue' {
2727
ProgressBar: typeof import('./components/ProgressBar.vue')['default']
2828
RouterLink: typeof import('vue-router')['RouterLink']
2929
RouterView: typeof import('vue-router')['RouterView']
30+
ScreenshotError: typeof import('./components/views/ScreenshotError.vue')['default']
3031
StatusIcon: typeof import('./components/StatusIcon.vue')['default']
3132
TestFilesEntry: typeof import('./components/dashboard/TestFilesEntry.vue')['default']
3233
TestsEntry: typeof import('./components/dashboard/TestsEntry.vue')['default']
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
file: string
4+
name: string
5+
url?: string
6+
}>()
7+
const emit = defineEmits<{ (e: 'close'): void }>()
8+
9+
onKeyStroke('Escape', () => {
10+
emit('close')
11+
})
12+
</script>
13+
14+
<template>
15+
<div w-350 max-w-screen h-full flex flex-col>
16+
<div p-4 relative border="base b">
17+
<p>Screenshot error</p>
18+
<p op50 font-mono text-sm>
19+
{{ file }}
20+
</p>
21+
<p op50 font-mono text-sm>
22+
{{ name }}
23+
</p>
24+
<IconButton
25+
icon="i-carbon:close"
26+
title="Close"
27+
absolute
28+
top-5px
29+
right-5px
30+
text-2xl
31+
@click="emit('close')"
32+
/>
33+
</div>
34+
35+
<div class="scrolls" grid="~ cols-1 rows-[min-content]" p-4>
36+
<img
37+
v-if="url"
38+
:src="url"
39+
:alt="`Screenshot error for '${name}' test in file '${file}'`"
40+
border="base t r b l dotted red-500"
41+
>
42+
<div v-else>
43+
Something was wrong, the image cannot be resolved.
44+
</div>
45+
</div>
46+
</div>
47+
</template>
48+
49+
<style scoped>
50+
.scrolls {
51+
place-items: center;
52+
}
53+
</style>

‎packages/ui/client/components/views/ViewReport.vue

+58-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ErrorWithDiff, File, Suite, Task } from 'vitest'
33
import type Convert from 'ansi-to-html'
44
import { isDark } from '~/composables/dark'
55
import { createAnsiToHtmlFilter } from '~/composables/error'
6-
import { config } from '~/composables/client'
6+
import { browserState, config } from '~/composables/client'
77
import { escapeHtml } from '~/utils/escape'
88
99
const props = defineProps<{
@@ -103,6 +103,30 @@ const failed = computed(() => {
103103
? mapLeveledTaskStacks(isDark.value, failedFlatMap)
104104
: failedFlatMap
105105
})
106+
107+
function open(task: Task) {
108+
const filePath = task.meta?.failScreenshotPath
109+
if (filePath) {
110+
fetch(`/__open-in-editor?file=${encodeURIComponent(filePath)}`)
111+
}
112+
}
113+
114+
const showScreenshot = ref(false)
115+
const timestamp = ref(Date.now())
116+
const currentTask = ref<Task | undefined>()
117+
const currentScreenshotUrl = computed(() => {
118+
const file = currentTask.value?.meta.failScreenshotPath
119+
// force refresh
120+
const t = timestamp.value
121+
// browser plugin using /, change this if base can be modified
122+
return file ? `/__screenshot-error?file=${encodeURIComponent(file)}&t=${t}` : undefined
123+
})
124+
125+
function showScreenshotModal(task: Task) {
126+
currentTask.value = task
127+
timestamp.value = Date.now()
128+
showScreenshot.value = true
129+
}
106130
</script>
107131

108132
<template>
@@ -121,7 +145,25 @@ const failed = computed(() => {
121145
}rem`,
122146
}"
123147
>
124-
{{ task.name }}
148+
<div flex="~ gap-2 items-center">
149+
<span>{{ task.name }}</span>
150+
<template v-if="browserState && task.meta?.failScreenshotPath">
151+
<IconButton
152+
v-tooltip.bottom="'View screenshot error'"
153+
class="!op-100"
154+
icon="i-carbon:image"
155+
title="View screenshot error"
156+
@click="showScreenshotModal(task)"
157+
/>
158+
<IconButton
159+
v-tooltip.bottom="'Open screenshot error in editor'"
160+
class="!op-100"
161+
icon="i-carbon:image-reference"
162+
title="Open screenshot error in editor"
163+
@click="open(task)"
164+
/>
165+
</template>
166+
</div>
125167
<div
126168
v-if="task.result?.htmlError"
127169
class="scrolls scrolls-rounded task-error"
@@ -146,6 +188,20 @@ const failed = computed(() => {
146188
All tests passed in this file
147189
</div>
148190
</template>
191+
<template v-if="browserState">
192+
<Modal v-model="showScreenshot" direction="right">
193+
<template v-if="currentTask">
194+
<Suspense>
195+
<ScreenshotError
196+
:file="currentTask.file.filepath"
197+
:name="currentTask.name"
198+
:url="currentScreenshotUrl"
199+
@close="showScreenshot = false"
200+
/>
201+
</Suspense>
202+
</template>
203+
</Modal>
204+
</template>
149205
</div>
150206
</template>
151207

0 commit comments

Comments
 (0)
Please sign in to comment.