refactor(e2e): replace test.skip with grep tags for device filtering

Use @desktop/@tablet/@mobile/@all tags in test titles and project-level
grep patterns instead of runtime test.skip(). This eliminates ~90
spurious skipped tests — each project now only loads tests tagged for
its device.

Before: 144 total (43 pass, 101 skip)
After:  55 total (43 pass, 12 skip — admin-only skips)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saltbo
2026-04-12 20:44:21 -04:00
parent e3b257e2ba
commit 6c1d7dc062
8 changed files with 82 additions and 121 deletions
+4 -7
View File
@@ -1,12 +1,12 @@
import { expect, test } from '@playwright/test'
test.describe('Auth flow', () => {
test('redirects to sign-in when not authenticated', async ({ page }) => {
test('redirects to sign-in when not authenticated @all', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveURL(/sign-in/, { timeout: 5000 })
})
test('sign-up and redirect to files', async ({ page }) => {
test('sign-up and redirect to files @all', async ({ page }) => {
await page.goto('/sign-up')
await expect(page.getByRole('heading', { name: 'ZPan' })).toBeVisible()
@@ -24,7 +24,7 @@ test.describe('Auth flow', () => {
await expect(page).toHaveURL(/files/, { timeout: 10000 })
})
test('sign-in with existing account', async ({ page }) => {
test('sign-in with existing account @all', async ({ page }) => {
const email = `login-${Date.now()}@example.com`
// Register via UI
@@ -54,10 +54,7 @@ test.describe('Auth flow', () => {
await expect(page).toHaveURL(/files/, { timeout: 10000 })
})
test('sidebar shows only My Files and Trash for regular users', async ({ page }, testInfo) => {
// Mobile sidebar is tested separately in responsive.spec.ts
test.skip(testInfo.project.name === 'mobile', 'mobile sidebar tested in responsive.spec.ts')
test('sidebar shows only My Files and Trash for regular users @desktop @tablet', async ({ page }) => {
await page.goto('/sign-up')
await page.getByLabel('Name').fill('Sidebar Test')
await page.getByLabel('Email').fill(`sidebar-${Date.now()}@example.com`)
+21 -33
View File
@@ -27,16 +27,14 @@ async function signUpAndGoToAdmin(page: import('@playwright/test').Page) {
// Admin sidebar
// ---------------------------------------------------------------------------
test.describe('Admin sidebar responsive', () => {
test('desktop: admin sidebar is visible', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'desktop only')
test('desktop: admin sidebar is visible @desktop', async ({ page }) => {
await signUpAndGoToAdmin(page)
const sidebar = page.locator('[data-slot="sidebar"]')
await expect(sidebar).toBeVisible()
})
test('mobile: admin sidebar opens via trigger', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: admin sidebar opens via trigger @mobile', async ({ page }) => {
await signUpAndGoToAdmin(page)
const sidebar = page.locator('[data-slot="sidebar"]')
@@ -53,8 +51,7 @@ test.describe('Admin sidebar responsive', () => {
// Admin storages page
// ---------------------------------------------------------------------------
test.describe('Admin storages page responsive', () => {
test('desktop: all storage table columns visible', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'desktop only')
test('desktop: all storage table columns visible @desktop', async ({ page }) => {
await signUpAndGoToAdmin(page)
const table = page.locator('table')
@@ -63,8 +60,7 @@ test.describe('Admin storages page responsive', () => {
await expect(table.locator('th', { hasText: /endpoint/i })).toBeVisible()
})
test('mobile: storage page has no horizontal overflow', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: storage page has no horizontal overflow @mobile', async ({ page }) => {
await signUpAndGoToAdmin(page)
const hasHScroll = await page.evaluate(
@@ -73,8 +69,7 @@ test.describe('Admin storages page responsive', () => {
expect(hasHScroll).toBe(false)
})
test('mobile: secondary storage columns are hidden', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: secondary storage columns are hidden @mobile', async ({ page }) => {
await signUpAndGoToAdmin(page)
const table = page.locator('table')
@@ -83,8 +78,7 @@ test.describe('Admin storages page responsive', () => {
await expect(table.locator('th', { hasText: /title/i })).toBeVisible()
})
test('tablet: storage page has no horizontal overflow', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'tablet', 'tablet only')
test('tablet: storage page has no horizontal overflow @tablet', async ({ page }) => {
await signUpAndGoToAdmin(page)
const hasHScroll = await page.evaluate(
@@ -98,8 +92,7 @@ test.describe('Admin storages page responsive', () => {
// Admin users page
// ---------------------------------------------------------------------------
test.describe('Admin users page responsive', () => {
test('mobile: users page has no horizontal overflow', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: users page has no horizontal overflow @mobile', async ({ page }) => {
await signUpAndGoToAdmin(page)
await page.goto('/admin/users')
await page.waitForURL(/admin\/users/, { timeout: 10000 })
@@ -110,8 +103,7 @@ test.describe('Admin users page responsive', () => {
expect(hasHScroll).toBe(false)
})
test('mobile: secondary user columns are hidden', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: secondary user columns are hidden @mobile', async ({ page }) => {
await signUpAndGoToAdmin(page)
await page.goto('/admin/users')
await page.waitForURL(/admin\/users/, { timeout: 10000 })
@@ -127,25 +119,21 @@ test.describe('Admin users page responsive', () => {
// Admin settings page
// ---------------------------------------------------------------------------
test.describe('Admin settings page responsive', () => {
for (const device of ['desktop', 'tablet', 'mobile']) {
test(`${device}: settings page has no horizontal overflow`, async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== device, `${device} only`)
await signUpAndGoToAdmin(page)
await page.goto('/admin/settings')
await page.waitForLoadState('networkidle')
if (!page.url().includes('/admin')) {
test.skip(true, 'user is not admin')
}
test('settings page has no horizontal overflow @all', async ({ page }) => {
await signUpAndGoToAdmin(page)
await page.goto('/admin/settings')
await page.waitForLoadState('networkidle')
if (!page.url().includes('/admin')) {
test.skip(true, 'user is not admin')
}
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
}
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
test('mobile: settings form fields are usable', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: settings form fields are usable @mobile', async ({ page }) => {
await signUpAndGoToAdmin(page)
await page.goto('/admin/settings')
await page.waitForLoadState('networkidle')
+18 -24
View File
@@ -4,32 +4,27 @@ import { expect, test } from '@playwright/test'
// Auth pages: sign-in and sign-up should not overflow on any device
// ---------------------------------------------------------------------------
test.describe('Auth pages responsive layout', () => {
for (const device of ['desktop', 'tablet', 'mobile']) {
test(`${device}: sign-in page has no horizontal overflow`, async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== device, `${device} only`)
await page.goto('/sign-in')
await expect(page.getByRole('heading', { name: 'ZPan' })).toBeVisible()
test('sign-in page has no horizontal overflow @all', async ({ page }) => {
await page.goto('/sign-in')
await expect(page.getByRole('heading', { name: 'ZPan' })).toBeVisible()
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
test(`${device}: sign-up page has no horizontal overflow`, async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== device, `${device} only`)
await page.goto('/sign-up')
await expect(page.getByRole('heading', { name: 'ZPan' })).toBeVisible()
test('sign-up page has no horizontal overflow @all', async ({ page }) => {
await page.goto('/sign-up')
await expect(page.getByRole('heading', { name: 'ZPan' })).toBeVisible()
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
}
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
test('mobile: sign-in form fields are usable', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: sign-in form fields are usable @mobile', async ({ page }) => {
await page.goto('/sign-in')
// Form fields should be visible and fillable
@@ -50,8 +45,7 @@ test.describe('Auth pages responsive layout', () => {
expect(formOverflows).toBe(false)
})
test('mobile: sign-up form fields are usable', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: sign-up form fields are usable @mobile', async ({ page }) => {
await page.goto('/sign-up')
await expect(page.getByLabel(/name/i)).toBeVisible()
+4 -8
View File
@@ -5,8 +5,7 @@ import { createFolder, signUpAndGoToFiles } from './helpers'
// Dialogs responsive behavior
// ---------------------------------------------------------------------------
test.describe('File dialogs responsive', () => {
test('mobile: new folder dialog is usable', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: new folder dialog is usable @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
// Open new folder dialog
@@ -31,8 +30,7 @@ test.describe('File dialogs responsive', () => {
expect(overflows).toBe(false)
})
test('tablet: new folder dialog is usable', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'tablet', 'tablet only')
test('tablet: new folder dialog is usable @tablet', async ({ page }) => {
await signUpAndGoToFiles(page)
await page.getByRole('button', { name: /new folder|folder/i }).click()
@@ -43,8 +41,7 @@ test.describe('File dialogs responsive', () => {
await expect(dialog.getByRole('button', { name: /create/i })).toBeVisible()
})
test('mobile: rename dialog is usable', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: rename dialog is usable @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
await createFolder(page, 'rename-me')
@@ -70,8 +67,7 @@ test.describe('File dialogs responsive', () => {
expect(overflows).toBe(false)
})
test('mobile: move dialog is usable', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: move dialog is usable @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
await createFolder(page, 'move-me')
+5 -10
View File
@@ -57,8 +57,7 @@ async function setupPreviewMocks(page: Page, file: { name: string; type: string;
// Preview per file type: mobile drawer vs desktop dialog
// ---------------------------------------------------------------------------
test.describe('Preview with mocked files', () => {
test('mobile: text file renders in full-screen drawer', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: text file renders in full-screen drawer @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
await setupPreviewMocks(page, { name: 'readme.txt', type: 'text/plain', size: 50, fixture: 'sample.txt' })
@@ -88,8 +87,7 @@ test.describe('Preview with mocked files', () => {
}
})
test('mobile: markdown file renders in drawer', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: markdown file renders in drawer @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
await setupPreviewMocks(page, { name: 'docs.md', type: 'text/markdown', size: 44, fixture: 'sample.md' })
@@ -103,8 +101,7 @@ test.describe('Preview with mocked files', () => {
await expect(dialog.locator('p', { hasText: 'docs.md' })).toBeVisible()
})
test('mobile: code file renders in drawer', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: code file renders in drawer @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
await setupPreviewMocks(page, { name: 'config.json', type: 'application/json', size: 19, fixture: 'sample.json' })
@@ -118,8 +115,7 @@ test.describe('Preview with mocked files', () => {
await expect(dialog.locator('p', { hasText: 'config.json' })).toBeVisible()
})
test('desktop: text file renders in centered dialog', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'desktop only')
test('desktop: text file renders in centered dialog @desktop', async ({ page }) => {
await signUpAndGoToFiles(page)
await setupPreviewMocks(page, { name: 'notes.txt', type: 'text/plain', size: 50, fixture: 'sample.txt' })
@@ -146,8 +142,7 @@ test.describe('Preview with mocked files', () => {
// No overflow
// ---------------------------------------------------------------------------
test.describe('Preview no overflow', () => {
test('mobile: page has no horizontal overflow', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: page has no horizontal overflow @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
const hasHScroll = await page.evaluate(
+8 -12
View File
@@ -20,20 +20,16 @@ async function signUpAndGoToTrash(page: import('@playwright/test').Page) {
// Recycle bin page responsive
// ---------------------------------------------------------------------------
test.describe('Recycle bin responsive layout', () => {
for (const device of ['desktop', 'tablet', 'mobile']) {
test(`${device}: recycle bin has no horizontal overflow`, async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== device, `${device} only`)
await signUpAndGoToTrash(page)
test('recycle bin has no horizontal overflow @all', async ({ page }) => {
await signUpAndGoToTrash(page)
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
}
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
test('mobile: trash toolbar buttons are accessible', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: trash toolbar buttons are accessible @mobile', async ({ page }) => {
await signUpAndGoToTrash(page)
// Empty trash button should be visible (icon + text or icon-only)
+15 -26
View File
@@ -5,24 +5,21 @@ import { createFolder, signUpAndGoToFiles } from './helpers'
// Sidebar behavior per device
// ---------------------------------------------------------------------------
test.describe('Sidebar responsive behavior', () => {
test('desktop: sidebar is visible by default', async ({ page, browserName }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'desktop only')
test('desktop: sidebar is visible by default @desktop', async ({ page, browserName }) => {
await signUpAndGoToFiles(page)
const sidebar = page.locator('[data-slot="sidebar"]')
await expect(sidebar).toBeVisible()
})
test('tablet: sidebar is visible by default', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'tablet', 'tablet only')
test('tablet: sidebar is visible by default @tablet', async ({ page }) => {
await signUpAndGoToFiles(page)
const sidebar = page.locator('[data-slot="sidebar"]')
await expect(sidebar).toBeVisible()
})
test('mobile: sidebar is hidden, opens as sheet via trigger', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: sidebar is hidden, opens as sheet via trigger @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
// Sidebar should not be visible initially on mobile
@@ -43,8 +40,7 @@ test.describe('Sidebar responsive behavior', () => {
// Toolbar: no horizontal overflow, key buttons accessible
// ---------------------------------------------------------------------------
test.describe('Toolbar responsive layout', () => {
test('desktop: all toolbar buttons visible in one row', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'desktop only')
test('desktop: all toolbar buttons visible in one row @desktop', async ({ page }) => {
await signUpAndGoToFiles(page)
const toolbar = page.locator('[data-testid="files-toolbar"]')
@@ -60,8 +56,7 @@ test.describe('Toolbar responsive layout', () => {
await expect(page.getByPlaceholder(/search/i)).toBeVisible()
})
test('tablet: toolbar does not overflow horizontally', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'tablet', 'tablet only')
test('tablet: toolbar does not overflow horizontally @tablet', async ({ page }) => {
await signUpAndGoToFiles(page)
const toolbar = page.locator('[data-testid="files-toolbar"]')
@@ -73,8 +68,7 @@ test.describe('Toolbar responsive layout', () => {
await expect(page.getByRole('button', { name: 'Upload' })).toBeVisible()
})
test('mobile: toolbar does not overflow, upload button accessible', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: toolbar does not overflow, upload button accessible @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
const toolbar = page.locator('[data-testid="files-toolbar"]')
@@ -92,8 +86,7 @@ test.describe('Toolbar responsive layout', () => {
// Table: secondary columns hidden on small screens
// ---------------------------------------------------------------------------
test.describe('File table responsive columns', () => {
test('desktop: all columns visible (name, size, modified, actions)', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'desktop only')
test('desktop: all columns visible (name, size, modified, actions) @desktop', async ({ page }) => {
await signUpAndGoToFiles(page)
await createFolder(page, 'test-folder')
@@ -102,8 +95,7 @@ test.describe('File table responsive columns', () => {
await expect(page.getByRole('columnheader', { name: /modified/i })).toBeVisible()
})
test('mobile: size and modified columns are hidden', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile', 'mobile only')
test('mobile: size and modified columns are hidden @mobile', async ({ page }) => {
await signUpAndGoToFiles(page)
await createFolder(page, 'test-folder')
@@ -118,15 +110,12 @@ test.describe('File table responsive columns', () => {
// Page-level: no horizontal scroll on any device
// ---------------------------------------------------------------------------
test.describe('No horizontal overflow', () => {
for (const device of ['desktop', 'tablet', 'mobile']) {
test(`${device}: page has no horizontal scrollbar`, async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== device, `${device} only`)
await signUpAndGoToFiles(page)
test('page has no horizontal scrollbar @all', async ({ page }) => {
await signUpAndGoToFiles(page)
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
}
const hasHScroll = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
)
expect(hasHScroll).toBe(false)
})
})
+7 -1
View File
@@ -36,9 +36,14 @@ export default defineConfig({
trace: 'on-first-retry',
},
projects: [
{ name: 'desktop', use: { ...devices['Desktop Chrome'] } },
{
name: 'desktop',
grep: /@desktop|@all/,
use: { ...devices['Desktop Chrome'] },
},
{
name: 'tablet',
grep: /@tablet|@all/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 768, height: 1024 },
@@ -46,6 +51,7 @@ export default defineConfig({
},
{
name: 'mobile',
grep: /@mobile|@all/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 390, height: 844 },