E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
Deploy to Production / Build & Verify (push) Failing after 13s
Ping Search Engines / Notify Search Engines (push) Successful in 3s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 1s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 9m36s
E2E Test Suite / Form Interaction Tests (push) Failing after 12m6s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 11m46s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 9m31s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 11m5s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 15m24s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 6s
E2E Test Suite / Mobile Device Tests (push) Failing after 3h12m28s
Uptime Monitor / Health & Response Time (push) Successful in 5s
Uptime Monitor / SSL Certificate (push) Successful in 3s
Uptime Monitor / Send Alerts (push) Has been skipped
Uptime Monitor / Record Uptime Success (push) Successful in 2s
559 lines
20 KiB
TypeScript
559 lines
20 KiB
TypeScript
/**
|
|
* Bug Fix Verification Tests
|
|
* Targeted regression tests for 5 fixed bugs.
|
|
* These tests would have caught the original issues and will catch regressions.
|
|
*
|
|
* BUG-1: Duplicate footer on Portfolio page
|
|
* BUG-2: Blog page theme styling issues
|
|
* BUG-3: Read Blog post 500 error
|
|
* BUG-4: CORS issues on Contact/Newsletter forms
|
|
* BUG-5: Header text black in dark mode
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
// ============================================================
|
|
// BUG-1: Duplicate footer on Portfolio page
|
|
// ============================================================
|
|
test.describe('BUG-1: Portfolio page has exactly one footer', () => {
|
|
test('Portfolio page renders exactly one footer element', async ({ page }) => {
|
|
await page.goto('/portfolio', { waitUntil: 'domcontentloaded' });
|
|
|
|
const footers = page.locator('footer');
|
|
await expect(footers).toHaveCount(1);
|
|
});
|
|
|
|
test('Portfolio page footer is visible', async ({ page }) => {
|
|
await page.goto('/portfolio', { waitUntil: 'domcontentloaded' });
|
|
await expect(page.locator('footer')).toBeVisible();
|
|
});
|
|
|
|
test('Portfolio page has one header and one footer (no duplicates)', async ({ page }) => {
|
|
await page.goto('/portfolio', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Exactly one header
|
|
await expect(page.locator('#main-header')).toHaveCount(1);
|
|
|
|
// Exactly one footer
|
|
await expect(page.locator('footer')).toHaveCount(1);
|
|
});
|
|
|
|
test('Other pages also have exactly one footer (sanity check)', async ({ page }) => {
|
|
const pages = ['/', '/about', '/services', '/contact', '/blog'];
|
|
|
|
for (const url of pages) {
|
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
const count = await page.locator('footer').count();
|
|
expect(count, `${url} should have exactly 1 footer, found ${count}`).toBe(1);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// BUG-2: Blog page theme styling issues
|
|
// ============================================================
|
|
test.describe('BUG-2: Blog page theme styling', () => {
|
|
test('Blog index page loads without layout errors', async ({ page }) => {
|
|
const consoleErrors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
|
});
|
|
|
|
const response = await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
|
|
const critical = consoleErrors.filter(
|
|
(e) => !e.includes('favicon') && !e.includes('livereload') && !e.includes('manifest')
|
|
);
|
|
expect(critical).toHaveLength(0);
|
|
});
|
|
|
|
test('Blog page applies dark mode classes when dark mode is active', async ({ page }) => {
|
|
await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Force dark mode
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.add('dark');
|
|
localStorage.setItem('theme', 'dark');
|
|
});
|
|
|
|
await page.waitForTimeout(200);
|
|
|
|
// Verify dark class is applied to html element
|
|
const hasDarkClass = await page.evaluate(() =>
|
|
document.documentElement.classList.contains('dark')
|
|
);
|
|
expect(hasDarkClass).toBe(true);
|
|
});
|
|
|
|
test('Blog page hero section background is not transparent in dark mode', async ({ page }) => {
|
|
await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.add('dark');
|
|
});
|
|
|
|
await page.waitForTimeout(300);
|
|
|
|
// Hero section should have a visible background (secondary-900 dark gradient)
|
|
const heroSection = page.locator('section').first();
|
|
await expect(heroSection).toBeVisible();
|
|
|
|
const bgColor = await heroSection.evaluate((el) =>
|
|
window.getComputedStyle(el).backgroundColor
|
|
);
|
|
|
|
// Should NOT be pure white (rgb(255, 255, 255)) in dark mode
|
|
// The actual color depends on Tailwind's secondary-900, but it should be dark
|
|
expect(bgColor).not.toBe('rgba(0, 0, 0, 0)');
|
|
});
|
|
|
|
test('Blog h1 is visible in both light and dark mode', async ({ page }) => {
|
|
await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
await expect(page.locator('h1')).toBeVisible();
|
|
|
|
// Switch to dark mode
|
|
await page.evaluate(() => document.documentElement.classList.add('dark'));
|
|
await page.waitForTimeout(200);
|
|
|
|
// H1 still visible
|
|
await expect(page.locator('h1')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// BUG-3: Read Blog post 500 error
|
|
// ============================================================
|
|
test.describe('BUG-3: Blog post route never returns 500', () => {
|
|
test('Blog index page never returns 500', async ({ page }) => {
|
|
const response = await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).not.toBe(500);
|
|
expect(response?.status()).toBe(200);
|
|
});
|
|
|
|
test('Blog post with invalid slug redirects (never 500)', async ({ page }) => {
|
|
// A non-existent slug should return 404 redirect, not 500
|
|
const response = await page.goto('/blog/this-post-definitely-does-not-exist-xyz', {
|
|
waitUntil: 'domcontentloaded',
|
|
});
|
|
|
|
// Should redirect to /404 (200 for custom error page) or return actual 404
|
|
// Critical: MUST NOT be 500
|
|
const status = response?.status();
|
|
expect(status).not.toBe(500);
|
|
});
|
|
|
|
test('Blog post with empty slug path redirects to /blog (never 500)', async ({ page }) => {
|
|
// The slug guard redirects empty slug to /blog
|
|
const response = await page.goto('/blog/', { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).not.toBe(500);
|
|
});
|
|
|
|
test('First blog post (if exists) loads successfully without 500', async ({ page }) => {
|
|
await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
|
|
const firstPostLink = page.locator('a[href^="/blog/"]').first();
|
|
const hasPost = (await firstPostLink.count()) > 0;
|
|
|
|
if (!hasPost) {
|
|
test.skip(); // No posts in content collection — skip gracefully
|
|
return;
|
|
}
|
|
|
|
const postHref = await firstPostLink.getAttribute('href');
|
|
if (!postHref) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
|
|
const response = await page.goto(postHref, { waitUntil: 'domcontentloaded' });
|
|
|
|
// Critical assertion: never a 500
|
|
expect(response?.status()).not.toBe(500);
|
|
expect(response?.status()).toBe(200);
|
|
|
|
// Has rendered content (markdown rendered to HTML)
|
|
await expect(page.locator('h1')).toBeVisible();
|
|
const bodyText = await page.locator('body').innerText();
|
|
expect(bodyText.trim().length).toBeGreaterThan(100);
|
|
});
|
|
|
|
test('Blog post SSR renders with BaseLayout (header + footer present)', async ({ page }) => {
|
|
await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
|
|
const firstPostLink = page.locator('a[href^="/blog/"]').first();
|
|
if ((await firstPostLink.count()) === 0) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
|
|
await firstPostLink.click();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// BaseLayout provides these — presence confirms SSR completed without crash
|
|
await expect(page.locator('#main-header')).toBeAttached();
|
|
await expect(page.locator('footer')).toBeAttached();
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// BUG-4: CORS issues on Contact & Newsletter forms
|
|
// ============================================================
|
|
test.describe('BUG-4: CORS headers on API endpoints', () => {
|
|
test('Contact API OPTIONS preflight returns 204 with CORS headers', async ({ page }) => {
|
|
// Use fetch to send OPTIONS preflight
|
|
const result = await page.evaluate(async () => {
|
|
const res = await fetch('/api/contact', {
|
|
method: 'OPTIONS',
|
|
headers: {
|
|
Origin: 'http://localhost:10000',
|
|
'Access-Control-Request-Method': 'POST',
|
|
'Access-Control-Request-Headers': 'Content-Type',
|
|
},
|
|
});
|
|
return {
|
|
status: res.status,
|
|
allowOrigin: res.headers.get('access-control-allow-origin'),
|
|
allowMethods: res.headers.get('access-control-allow-methods'),
|
|
allowHeaders: res.headers.get('access-control-allow-headers'),
|
|
};
|
|
});
|
|
|
|
expect(result.status).toBe(204);
|
|
expect(result.allowOrigin).toBeTruthy();
|
|
expect(result.allowMethods).toContain('POST');
|
|
});
|
|
|
|
test('Newsletter API OPTIONS preflight returns 204 with CORS headers', async ({ page }) => {
|
|
const result = await page.evaluate(async () => {
|
|
const res = await fetch('/api/newsletter', {
|
|
method: 'OPTIONS',
|
|
headers: {
|
|
Origin: 'http://localhost:10000',
|
|
'Access-Control-Request-Method': 'POST',
|
|
'Access-Control-Request-Headers': 'Content-Type',
|
|
},
|
|
});
|
|
return {
|
|
status: res.status,
|
|
allowOrigin: res.headers.get('access-control-allow-origin'),
|
|
allowMethods: res.headers.get('access-control-allow-methods'),
|
|
};
|
|
});
|
|
|
|
expect(result.status).toBe(204);
|
|
expect(result.allowOrigin).toBeTruthy();
|
|
expect(result.allowMethods).toContain('POST');
|
|
});
|
|
|
|
test('Contact API POST returns CORS headers (not missing)', async ({ page }) => {
|
|
const result = await page.evaluate(async () => {
|
|
const res = await fetch('/api/contact', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: '', email: '', subject: '', message: '' }),
|
|
});
|
|
return {
|
|
status: res.status,
|
|
allowOrigin: res.headers.get('access-control-allow-origin'),
|
|
contentType: res.headers.get('content-type'),
|
|
};
|
|
});
|
|
|
|
// Status is validation error (422) or rate limit — but CORS headers must be present
|
|
expect(result.allowOrigin).toBeTruthy();
|
|
expect(result.contentType).toContain('application/json');
|
|
// Critical: must NOT be a CORS error (which would give status 0 or throw)
|
|
expect(result.status).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('Newsletter API POST returns CORS headers with invalid email', async ({ page }) => {
|
|
const result = await page.evaluate(async () => {
|
|
const res = await fetch('/api/newsletter', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email: 'not-valid-email' }),
|
|
});
|
|
return {
|
|
status: res.status,
|
|
allowOrigin: res.headers.get('access-control-allow-origin'),
|
|
};
|
|
});
|
|
|
|
expect(result.status).toBe(422); // Validation error
|
|
expect(result.allowOrigin).toBeTruthy(); // CORS header present
|
|
});
|
|
|
|
test('Contact form page submits without JS CORS error', async ({ page }) => {
|
|
const corsErrors: string[] = [];
|
|
|
|
// Catch CORS-related console errors
|
|
page.on('console', (msg) => {
|
|
if (
|
|
msg.type() === 'error' &&
|
|
(msg.text().includes('CORS') || msg.text().includes('Access-Control') || msg.text().includes('cross-origin'))
|
|
) {
|
|
corsErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
await page.goto('/contact', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Attempt a form submission with invalid data (will get 422, but no CORS error)
|
|
await page.fill('#name', 'Test');
|
|
await page.fill('#email', 'test@example.com');
|
|
await page.selectOption('#subject', 'web-development');
|
|
await page.fill('#message', 'This is a test submission for CORS verification');
|
|
await page.locator('#contact-form button[type="submit"]').click();
|
|
|
|
await page.waitForTimeout(1500);
|
|
|
|
expect(corsErrors, 'CORS errors detected on form submission').toHaveLength(0);
|
|
});
|
|
|
|
test('Newsletter form in footer submits without CORS error', async ({ page }) => {
|
|
const corsErrors: string[] = [];
|
|
|
|
page.on('console', (msg) => {
|
|
if (
|
|
msg.type() === 'error' &&
|
|
(msg.text().includes('CORS') || msg.text().includes('Access-Control'))
|
|
) {
|
|
corsErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Scroll to footer newsletter form
|
|
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
await page.waitForTimeout(300);
|
|
|
|
const newsletterInput = page.locator('footer input[type="email"]');
|
|
if (await newsletterInput.count() > 0) {
|
|
await newsletterInput.fill('test@example.com');
|
|
const submitBtn = page.locator('footer button[type="submit"]').first();
|
|
if (await submitBtn.count() > 0) {
|
|
await submitBtn.click();
|
|
await page.waitForTimeout(1000);
|
|
}
|
|
}
|
|
|
|
expect(corsErrors, 'CORS errors on newsletter form').toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// BUG-5: Header text black in dark mode
|
|
// ============================================================
|
|
test.describe('BUG-5: Header text readable in dark mode', () => {
|
|
test('WorkRoot logo text is white (not black) in dark mode', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Activate dark mode
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.add('dark');
|
|
localStorage.setItem('theme', 'dark');
|
|
});
|
|
|
|
await page.waitForTimeout(200);
|
|
|
|
// Find the WorkRoot logo span in the desktop header
|
|
const logoSpan = page.locator('#main-header span').filter({ hasText: 'WorkRoot' }).first();
|
|
await expect(logoSpan).toBeVisible();
|
|
|
|
const color = await logoSpan.evaluate((el) =>
|
|
window.getComputedStyle(el).color
|
|
);
|
|
|
|
// In dark mode, should be white (#ffffff = rgb(255, 255, 255))
|
|
// Original broken state was rgb(30, 41, 59) = #1e293b (near black)
|
|
expect(color).not.toBe('rgb(30, 41, 59)'); // NOT the old broken black color
|
|
expect(color).toBe('rgb(255, 255, 255)'); // IS the fixed white color
|
|
});
|
|
|
|
test('WorkRoot logo text is dark (not white) in light mode', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Ensure light mode
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.remove('dark');
|
|
localStorage.setItem('theme', 'light');
|
|
});
|
|
|
|
await page.waitForTimeout(200);
|
|
|
|
const logoSpan = page.locator('#main-header span').filter({ hasText: 'WorkRoot' }).first();
|
|
await expect(logoSpan).toBeVisible();
|
|
|
|
const color = await logoSpan.evaluate((el) =>
|
|
window.getComputedStyle(el).color
|
|
);
|
|
|
|
// In light mode, should be dark text (text-secondary = #1e293b)
|
|
expect(color).toBe('rgb(30, 41, 59)'); // Dark color in light mode is correct
|
|
});
|
|
|
|
test('Header logo is readable on all pages in dark mode', async ({ page }) => {
|
|
const testPages = ['/', '/about', '/services', '/portfolio', '/blog', '/contact'];
|
|
|
|
for (const url of testPages) {
|
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.add('dark');
|
|
});
|
|
|
|
await page.waitForTimeout(150);
|
|
|
|
const logoSpan = page.locator('#main-header span').filter({ hasText: 'WorkRoot' }).first();
|
|
await expect(logoSpan).toBeVisible();
|
|
|
|
const color = await logoSpan.evaluate((el) =>
|
|
window.getComputedStyle(el).color
|
|
);
|
|
|
|
// Must NOT be the broken black color
|
|
expect(
|
|
color,
|
|
`Logo text on ${url} in dark mode should be white, not black`
|
|
).not.toBe('rgb(30, 41, 59)');
|
|
}
|
|
});
|
|
|
|
test('Mobile menu WorkRoot text is white in dark mode', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Activate dark mode
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.add('dark');
|
|
});
|
|
|
|
// Open mobile menu
|
|
await page.locator('#mobile-menu-toggle').click();
|
|
await page.waitForTimeout(400);
|
|
|
|
// Mobile menu logo span
|
|
const mobileLogoSpan = page.locator('#mobile-menu span').filter({ hasText: 'WorkRoot' }).first();
|
|
|
|
// May or may not have a WorkRoot span in mobile menu, check gracefully
|
|
if ((await mobileLogoSpan.count()) > 0) {
|
|
const color = await mobileLogoSpan.evaluate((el) =>
|
|
window.getComputedStyle(el).color
|
|
);
|
|
expect(color).not.toBe('rgb(30, 41, 59)');
|
|
}
|
|
});
|
|
|
|
test('Header contrast is WCAG AA compliant in dark mode', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.add('dark');
|
|
});
|
|
|
|
await page.waitForTimeout(200);
|
|
|
|
const header = page.locator('#main-header');
|
|
const logoSpan = page.locator('#main-header span').filter({ hasText: 'WorkRoot' }).first();
|
|
|
|
const headerBg = await header.evaluate((el) =>
|
|
window.getComputedStyle(el).backgroundColor
|
|
);
|
|
const textColor = await logoSpan.evaluate((el) =>
|
|
window.getComputedStyle(el).color
|
|
);
|
|
|
|
// Both colors are set — not transparent
|
|
expect(headerBg).not.toBe('rgba(0, 0, 0, 0)');
|
|
expect(textColor).not.toBe('rgba(0, 0, 0, 0)');
|
|
|
|
// White text on dark bg = ~17.5:1 contrast ratio (WAY above 4.5:1 AA)
|
|
// Just verify it's not the broken identical-to-background color
|
|
expect(textColor).not.toBe(headerBg);
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// Cross-cutting: All fixes together — full page smoke
|
|
// ============================================================
|
|
test.describe('All Fixes: Full page smoke test', () => {
|
|
test('Home page loads cleanly with correct layout', async ({ page }) => {
|
|
const response = await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('#main-header')).toHaveCount(1);
|
|
await expect(page.locator('footer')).toHaveCount(1);
|
|
});
|
|
|
|
test('Portfolio page loads with single footer and working filters', async ({ page }) => {
|
|
await page.goto('/portfolio', { waitUntil: 'domcontentloaded' });
|
|
await expect(page.locator('footer')).toHaveCount(1);
|
|
await expect(page.locator('.project-card').first()).toBeVisible();
|
|
await page.locator('button[data-filter="web"]').click();
|
|
await page.waitForTimeout(400);
|
|
expect(await page.locator('.project-card:not(.hidden)').count()).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('Blog page loads without 500 and with correct theme structure', async ({ page }) => {
|
|
const response = await page.goto('/blog', { waitUntil: 'domcontentloaded' });
|
|
expect(response?.status()).not.toBe(500);
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page.locator('h1')).toBeVisible();
|
|
await expect(page.locator('footer')).toHaveCount(1);
|
|
});
|
|
|
|
test('Contact page has form with working API endpoint (no CORS error)', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') errors.push(msg.text());
|
|
});
|
|
|
|
await page.goto('/contact', { waitUntil: 'domcontentloaded' });
|
|
await expect(page.locator('#contact-form')).toBeVisible();
|
|
|
|
// Verify OPTIONS preflight works (simulates browser CORS check)
|
|
const preflightResult = await page.evaluate(async () => {
|
|
try {
|
|
const res = await fetch('/api/contact', { method: 'OPTIONS' });
|
|
return { ok: true, status: res.status };
|
|
} catch {
|
|
return { ok: false, status: 0 };
|
|
}
|
|
});
|
|
|
|
expect(preflightResult.ok).toBe(true);
|
|
expect(preflightResult.status).toBe(204);
|
|
|
|
const criticalErrors = errors.filter(
|
|
(e) => !e.includes('favicon') && !e.includes('manifest') && !e.includes('livereload')
|
|
);
|
|
expect(criticalErrors).toHaveLength(0);
|
|
});
|
|
|
|
test('Dark mode toggle works and header stays readable', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
|
|
// Toggle dark mode via ThemeToggle button
|
|
const themeToggle = page.locator('#theme-toggle').first();
|
|
if (await themeToggle.count() > 0) {
|
|
await themeToggle.click();
|
|
await page.waitForTimeout(300);
|
|
} else {
|
|
// Force dark mode programmatically
|
|
await page.evaluate(() => document.documentElement.classList.add('dark'));
|
|
await page.waitForTimeout(200);
|
|
}
|
|
|
|
// Header is visible
|
|
await expect(page.locator('#main-header')).toBeVisible();
|
|
|
|
// Logo text exists and is not black
|
|
const logoSpan = page.locator('#main-header span').filter({ hasText: 'WorkRoot' }).first();
|
|
if (await logoSpan.count() > 0) {
|
|
const color = await logoSpan.evaluate((el) => window.getComputedStyle(el).color);
|
|
expect(color).not.toBe('rgb(30, 41, 59)');
|
|
}
|
|
});
|
|
});
|