Skip to content

Commit

Permalink
feat(browser): allow preview and open in the editor screenshot error …
Browse files Browse the repository at this point in the history
…from ui (#6113)
  • Loading branch information
userquin committed Jul 15, 2024
1 parent 3826941 commit 2d62051
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 4 deletions.
51 changes: 49 additions & 2 deletions packages/browser/src/node/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { readFileSync } from 'node:fs'
import { basename, resolve } from 'pathe'
import { lstatSync, readFileSync } from 'node:fs'
import type { Stats } from 'node:fs'
import { basename, extname, resolve } from 'pathe'
import sirv from 'sirv'
import type { WorkspaceProject } from 'vitest/node'
import { getFilePoolName, resolveApiServerConfig, resolveFsAllow, distDir as vitestDist } from 'vitest/node'
Expand Down Expand Up @@ -100,6 +101,52 @@ export default (browserServer: BrowserServer, base = '/'): Plugin[] => {
},
}),
)

const screenshotFailures = project.config.browser.ui && project.config.browser.screenshotFailures

// eslint-disable-next-line prefer-arrow-callback
screenshotFailures && server.middlewares.use(`${base}__screenshot-error`, function vitestBrowserScreenshotError(req, res) {
if (!req.url || !browserServer.provider) {
res.statusCode = 404
res.end()
return
}

const url = new URL(req.url, 'http://localhost')
const file = url.searchParams.get('file')
if (!file) {
res.statusCode = 404
res.end()
return
}

let stat: Stats | undefined
try {
stat = lstatSync(file)
}
catch (_) {
}

if (!stat?.isFile()) {
res.statusCode = 404
res.end()
return
}

const ext = extname(file)
const buffer = readFileSync(file)
res.setHeader(
'Cache-Control',
'public,max-age=0,must-revalidate',
)
res.setHeader('Content-Length', buffer.length)
res.setHeader('Content-Type', ext === 'jpeg' || ext === 'jpg'
? 'image/jpeg'
: ext === 'webp'
? 'image/webp'
: 'image/png')
res.end(buffer)
})
},
},
{
Expand Down
1 change: 1 addition & 0 deletions packages/ui/client/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ declare module 'vue' {
ProgressBar: typeof import('./components/ProgressBar.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScreenshotError: typeof import('./components/views/ScreenshotError.vue')['default']
StatusIcon: typeof import('./components/StatusIcon.vue')['default']
TestFilesEntry: typeof import('./components/dashboard/TestFilesEntry.vue')['default']
TestsEntry: typeof import('./components/dashboard/TestsEntry.vue')['default']
Expand Down
53 changes: 53 additions & 0 deletions packages/ui/client/components/views/ScreenshotError.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script setup lang="ts">
defineProps<{
file: string
name: string
url?: string
}>()
const emit = defineEmits<{ (e: 'close'): void }>()
onKeyStroke('Escape', () => {
emit('close')
})
</script>

<template>
<div w-350 max-w-screen h-full flex flex-col>
<div p-4 relative border="base b">
<p>Screenshot error</p>
<p op50 font-mono text-sm>
{{ file }}
</p>
<p op50 font-mono text-sm>
{{ name }}
</p>
<IconButton
icon="i-carbon:close"
title="Close"
absolute
top-5px
right-5px
text-2xl
@click="emit('close')"
/>
</div>

<div class="scrolls" grid="~ cols-1 rows-[min-content]" p-4>
<img
v-if="url"
:src="url"
:alt="`Screenshot error for '${name}' test in file '${file}'`"
border="base t r b l dotted red-500"
>
<div v-else>
Something was wrong, the image cannot be resolved.
</div>
</div>
</div>
</template>

<style scoped>
.scrolls {
place-items: center;
}
</style>
60 changes: 58 additions & 2 deletions packages/ui/client/components/views/ViewReport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ErrorWithDiff, File, Suite, Task } from 'vitest'
import type Convert from 'ansi-to-html'
import { isDark } from '~/composables/dark'
import { createAnsiToHtmlFilter } from '~/composables/error'
import { config } from '~/composables/client'
import { browserState, config } from '~/composables/client'
import { escapeHtml } from '~/utils/escape'
const props = defineProps<{
Expand Down Expand Up @@ -103,6 +103,30 @@ const failed = computed(() => {
? mapLeveledTaskStacks(isDark.value, failedFlatMap)
: failedFlatMap
})
function open(task: Task) {
const filePath = task.meta?.failScreenshotPath
if (filePath) {
fetch(`/__open-in-editor?file=${encodeURIComponent(filePath)}`)
}
}
const showScreenshot = ref(false)
const timestamp = ref(Date.now())
const currentTask = ref<Task | undefined>()
const currentScreenshotUrl = computed(() => {
const file = currentTask.value?.meta.failScreenshotPath
// force refresh
const t = timestamp.value
// browser plugin using /, change this if base can be modified
return file ? `/__screenshot-error?file=${encodeURIComponent(file)}&t=${t}` : undefined
})
function showScreenshotModal(task: Task) {
currentTask.value = task
timestamp.value = Date.now()
showScreenshot.value = true
}
</script>

<template>
Expand All @@ -121,7 +145,25 @@ const failed = computed(() => {
}rem`,
}"
>
{{ task.name }}
<div flex="~ gap-2 items-center">
<span>{{ task.name }}</span>
<template v-if="browserState && task.meta?.failScreenshotPath">
<IconButton
v-tooltip.bottom="'View screenshot error'"
class="!op-100"
icon="i-carbon:image"
title="View screenshot error"
@click="showScreenshotModal(task)"
/>
<IconButton
v-tooltip.bottom="'Open screenshot error in editor'"
class="!op-100"
icon="i-carbon:image-reference"
title="Open screenshot error in editor"
@click="open(task)"
/>
</template>
</div>
<div
v-if="task.result?.htmlError"
class="scrolls scrolls-rounded task-error"
Expand All @@ -146,6 +188,20 @@ const failed = computed(() => {
All tests passed in this file
</div>
</template>
<template v-if="browserState">
<Modal v-model="showScreenshot" direction="right">
<template v-if="currentTask">
<Suspense>
<ScreenshotError
:file="currentTask.file.filepath"
:name="currentTask.name"
:url="currentScreenshotUrl"
@close="showScreenshot = false"
/>
</Suspense>
</template>
</Modal>
</template>
</div>
</template>
Expand Down

0 comments on commit 2d62051

Please sign in to comment.