From 6c1d7dc062d49ddd3836aef6a8efadb35d01278e Mon Sep 17 00:00:00 2001 From: saltbo Date: Sun, 12 Apr 2026 20:44:21 -0400 Subject: [PATCH] refactor(e2e): replace test.skip with grep tags for device filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- e2e/auth.spec.ts | 11 +++---- e2e/responsive-admin.spec.ts | 54 +++++++++++++--------------------- e2e/responsive-auth.spec.ts | 42 ++++++++++++-------------- e2e/responsive-dialogs.spec.ts | 12 +++----- e2e/responsive-preview.spec.ts | 15 ++++------ e2e/responsive-trash.spec.ts | 20 +++++-------- e2e/responsive.spec.ts | 41 ++++++++++---------------- playwright.config.ts | 8 ++++- 8 files changed, 82 insertions(+), 121 deletions(-) diff --git a/e2e/auth.spec.ts b/e2e/auth.spec.ts index 4c1ec45d..8361c7db 100644 --- a/e2e/auth.spec.ts +++ b/e2e/auth.spec.ts @@ -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`) diff --git a/e2e/responsive-admin.spec.ts b/e2e/responsive-admin.spec.ts index 310616ba..7682c512 100644 --- a/e2e/responsive-admin.spec.ts +++ b/e2e/responsive-admin.spec.ts @@ -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') diff --git a/e2e/responsive-auth.spec.ts b/e2e/responsive-auth.spec.ts index 9023c16a..29e2d257 100644 --- a/e2e/responsive-auth.spec.ts +++ b/e2e/responsive-auth.spec.ts @@ -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() diff --git a/e2e/responsive-dialogs.spec.ts b/e2e/responsive-dialogs.spec.ts index 9889b452..399fbb00 100644 --- a/e2e/responsive-dialogs.spec.ts +++ b/e2e/responsive-dialogs.spec.ts @@ -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') diff --git a/e2e/responsive-preview.spec.ts b/e2e/responsive-preview.spec.ts index 0d4897fb..8663059d 100644 --- a/e2e/responsive-preview.spec.ts +++ b/e2e/responsive-preview.spec.ts @@ -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( diff --git a/e2e/responsive-trash.spec.ts b/e2e/responsive-trash.spec.ts index d3b51858..c6781e07 100644 --- a/e2e/responsive-trash.spec.ts +++ b/e2e/responsive-trash.spec.ts @@ -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) diff --git a/e2e/responsive.spec.ts b/e2e/responsive.spec.ts index cea87b3f..653395c7 100644 --- a/e2e/responsive.spec.ts +++ b/e2e/responsive.spec.ts @@ -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) + }) }) diff --git a/playwright.config.ts b/playwright.config.ts index 34510a9c..353d1f58 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -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 },