142 lines
4.9 KiB
JavaScript
142 lines
4.9 KiB
JavaScript
// End-to-end preview test using Playwright and a provided session.
|
|
// Requires a valid session token in env PREVIEW_SESSION and a design ID in PREVIEW_DESIGN_ID.
|
|
// Example: PREVIEW_SESSION=abc PREVIEW_DESIGN_ID=design_123 pnpm node tests/preview-e2e.js
|
|
|
|
const { chromium } = require('playwright')
|
|
|
|
async function main() {
|
|
const session = process.env.PREVIEW_SESSION
|
|
const designId = process.env.PREVIEW_DESIGN_ID
|
|
if (!session || !designId) {
|
|
console.error('Set PREVIEW_SESSION and PREVIEW_DESIGN_ID env vars.')
|
|
process.exit(1)
|
|
}
|
|
|
|
const browser = await chromium.launch({ headless: true })
|
|
const page = await browser.newPage()
|
|
const errors = []
|
|
const exportStatuses = []
|
|
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(`console error: ${msg.text()}`)
|
|
})
|
|
page.on('pageerror', (err) => errors.push(`page error: ${err.message}`))
|
|
|
|
// capture export job status responses
|
|
page.on('response', async (res) => {
|
|
const url = res.url()
|
|
if (url.includes('/exports/')) {
|
|
try {
|
|
const json = await res.json()
|
|
exportStatuses.push({ url, status: res.status(), body: json })
|
|
} catch {
|
|
exportStatuses.push({ url, status: res.status(), body: 'non-json' })
|
|
}
|
|
}
|
|
})
|
|
|
|
// prime persisted store so app is "connected" (same origin needed)
|
|
await page.goto('http://localhost:3000', { waitUntil: 'load' })
|
|
await page.evaluate(
|
|
({ session }) => {
|
|
const payload = {
|
|
isConnected: true,
|
|
user: { name: 'Playwright User' },
|
|
accessToken: 'dummy',
|
|
refreshToken: 'dummy',
|
|
canvaUserId: 'dummy',
|
|
initializing: false,
|
|
sessionId: session
|
|
}
|
|
localStorage.setItem('canva-store', JSON.stringify(payload))
|
|
},
|
|
{ session }
|
|
)
|
|
|
|
// Hit API directly to create export (bypass UI flake)
|
|
const apiBase = 'http://127.0.0.1:4000'
|
|
const createRes = await fetch(`${apiBase}/exports?session=${encodeURIComponent(session)}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ design_id: designId, format: { type: 'png' } })
|
|
})
|
|
if (!createRes.ok) {
|
|
throw new Error(`Failed to create export: ${createRes.status} ${await createRes.text()}`)
|
|
}
|
|
const job = await createRes.json()
|
|
const jobId =
|
|
job?.id || job?.export_id || job?.export?.id || job?.job_id || job?.job?.id
|
|
if (!jobId) throw new Error('No export job id returned from API')
|
|
|
|
let exportUrl = null
|
|
for (let i = 0; i < 60; i++) {
|
|
await new Promise((r) => setTimeout(r, 1000))
|
|
const statusRes = await fetch(`${apiBase}/exports/${encodeURIComponent(jobId)}?session=${encodeURIComponent(session)}`)
|
|
const statusJson = await statusRes.json()
|
|
exportStatuses.push({ url: statusRes.url, status: statusRes.status, body: statusJson })
|
|
const urls = statusJson?.job?.urls || statusJson?.urls
|
|
if (statusJson?.job?.status === 'success' && Array.isArray(urls) && urls[0]) {
|
|
exportUrl = urls[0]
|
|
break
|
|
}
|
|
if (statusJson?.job?.status === 'failed') {
|
|
errors.push(`Export failed: ${JSON.stringify(statusJson)}`)
|
|
break
|
|
}
|
|
}
|
|
|
|
if (!exportUrl) {
|
|
errors.push('Export never produced a URL')
|
|
}
|
|
|
|
// Load page so hooks mount, then inject latest export into window and dispatch event
|
|
const url = `http://localhost:3000/designs?session=${encodeURIComponent(session)}`
|
|
await page.goto(url, { waitUntil: 'networkidle' })
|
|
await page.waitForTimeout(500)
|
|
await page.evaluate(
|
|
({ exportUrl }) => {
|
|
window.__latestExport = { url: exportUrl, type: 'png' }
|
|
window.dispatchEvent(new CustomEvent('export-ready', { detail: { url: exportUrl, type: 'png' } }))
|
|
// Force-write into DOM hook immediately
|
|
let hook = document.getElementById('export-latest-hook')
|
|
if (!hook) {
|
|
hook = document.createElement('div')
|
|
hook.id = 'export-latest-hook'
|
|
hook.setAttribute('aria-hidden', 'true')
|
|
hook.style.display = 'none'
|
|
document.body.appendChild(hook)
|
|
}
|
|
hook.textContent = exportUrl || ''
|
|
},
|
|
{ exportUrl }
|
|
)
|
|
|
|
// wait for DOM hook to reflect
|
|
const hookText = await page.waitForSelector('#export-latest-hook', { timeout: 10000 })
|
|
.then((el) => el.textContent())
|
|
.catch(() => '')
|
|
if (!hookText || hookText.trim() === '') {
|
|
errors.push('No latest-export link rendered')
|
|
} else {
|
|
console.log('Found export via DOM hook:', hookText.trim())
|
|
}
|
|
|
|
await browser.close()
|
|
|
|
if (errors.length) {
|
|
console.error('Preview E2E failed:')
|
|
errors.forEach((e) => console.error(' -', e))
|
|
if (exportStatuses.length) {
|
|
console.error('Export statuses captured:')
|
|
exportStatuses.forEach((s) => console.error(` - [${s.status}] ${s.url} => ${JSON.stringify(s.body)}`))
|
|
}
|
|
process.exit(1)
|
|
}
|
|
console.log('Preview E2E passed: preview rendered without console/page errors')
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error(e)
|
|
process.exit(1)
|
|
})
|