personal-lifecycle-tracker / e2e / benchmark.spec.ts
benchmark.spec.ts
Raw
import { test, expect } from '@playwright/test';

test.beforeAll(async () => {
  // Pre-warm CloudFront edge cache and AWS Lambda backend to avoid initial cold-start latency penalties
  try {
    await Promise.allSettled([
      fetch('https://d1ca9upoitnq9b.cloudfront.net/', { signal: AbortSignal.timeout(5000) }),
      fetch('https://7ksomsylvke2uawuhhccbfr6hy0agzyb.lambda-url.ap-south-1.on.aws/health', { signal: AbortSignal.timeout(5000) }),
    ]);
  } catch {
    // Ignore warm-up network errors if offline
  }
});

test('benchmark page load time in browser', async ({ page }) => {
  const start = Date.now();

  await page.goto('/', { waitUntil: 'domcontentloaded' });
  const domContentLoadedTime = Date.now() - start;

  // Wait for user name in header and items table or empty state to appear
  await expect(page.locator('header').getByText('Alex Taylor')).toBeVisible();
  const fullyRenderedTime = Date.now() - start;

  console.log(`\n========================================`);
  console.log(`DOM Content Loaded Time: ${domContentLoadedTime} ms`);
  console.log(`Fully Rendered Page Time: ${fullyRenderedTime} ms (${(fullyRenderedTime / 1000).toFixed(2)}s)`);
  console.log(`========================================\n`);

  expect(fullyRenderedTime).toBeLessThan(3000);
});

test('benchmark page load time on CloudFront directly', async ({ page }) => {
  const start = Date.now();
  const reqTimes = new Map<string, number>();
  page.on('console', msg => console.log(`[Browser Console +${Date.now() - start}ms] ${msg.type()}: ${msg.text()}`));
  page.on('request', request => {
    reqTimes.set(request.url(), Date.now());
    console.log(`[Req Started +${Date.now() - start}ms] ${request.method()} ${request.url()}`);
  });
  page.on('response', response => {
    const reqStart = reqTimes.get(response.url()) || start;
    console.log(`[Res Finished +${Date.now() - start}ms] (duration: ${Date.now() - reqStart}ms) ${response.url()} -> ${response.status()}`);
  });

  await page.goto('https://d1ca9upoitnq9b.cloudfront.net/', { waitUntil: 'domcontentloaded' });
  const domContentLoadedTime = Date.now() - start;

  await expect(page.locator('header').getByText('Alex Taylor')).toBeVisible();
  const fullyRenderedTime = Date.now() - start;

  console.log(`\n========================================`);
  console.log(`[CloudFront Direct] DOM Content Loaded Time: ${domContentLoadedTime} ms`);
  console.log(`[CloudFront Direct] Fully Rendered Page Time: ${fullyRenderedTime} ms (${(fullyRenderedTime / 1000).toFixed(2)}s)`);
  console.log(`========================================\n`);

  // Target: <2s locally, with up to 7.5s allowed in CI to accommodate cross-continental public internet latency & cold starts
  const threshold = process.env.CI ? 7500 : 3500;
  expect(fullyRenderedTime).toBeLessThan(threshold);
});

test('benchmark page load time on CloudFront with authenticated session', async ({ page }) => {
  const start = Date.now();
  await page.addInitScript(() => {
    localStorage.setItem('plt_auth_token_v1', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyX2FsZXgiLCJlbWFpbCI6ImFsZXhAZXhhbXBsZS5jb20iLCJleHAiOjE3OTI1NzA5ODl9.kVuQPUqhiFd3Ea3ijJk9mNWSQmGav4UK6JWm0dz3fok');
    localStorage.setItem('plt_current_user_id_v1', 'user_alex');
  });

  await page.goto('https://d1ca9upoitnq9b.cloudfront.net/', { waitUntil: 'domcontentloaded' });
  const domContentLoadedTime = Date.now() - start;

  await expect(page.locator('header').getByText('Alex Taylor')).toBeVisible();
  const fullyRenderedTime = Date.now() - start;

  console.log(`\n========================================`);
  console.log(`[CloudFront Authenticated] DOM Content Loaded Time: ${domContentLoadedTime} ms`);
  console.log(`[CloudFront Authenticated] Fully Rendered Page Time: ${fullyRenderedTime} ms (${(fullyRenderedTime / 1000).toFixed(2)}s)`);
  console.log(`========================================\n`);

  // Target: <1.5s locally, with up to 6.0s allowed in CI to accommodate cross-continental public internet latency
  const threshold = process.env.CI ? 6000 : 3000;
  expect(fullyRenderedTime).toBeLessThan(threshold);
});

test('benchmark page load time on localhost:3003 in browser', async ({ page }) => {
  const start = Date.now();

  await page.goto('http://localhost:3003/', { waitUntil: 'domcontentloaded' });
  const domContentLoadedTime = Date.now() - start;

  await expect(page.locator('header').getByText('Alex Taylor')).toBeVisible();
  const fullyRenderedTime = Date.now() - start;

  console.log(`\n========================================`);
  console.log(`[localhost:3003] DOM Content Loaded Time: ${domContentLoadedTime} ms`);
  console.log(`[localhost:3003] Fully Rendered Page Time: ${fullyRenderedTime} ms (${(fullyRenderedTime / 1000).toFixed(2)}s)`);
  console.log(`========================================\n`);

  expect(fullyRenderedTime).toBeLessThan(3000);
});

test('benchmark page load time in browser with live AWS Lambda backend', async ({ page }) => {
  const awsUrl = 'https://7ksomsylvke2uawuhhccbfr6hy0agzyb.lambda-url.ap-south-1.on.aws';

  await page.route('**/api/v1/**', async (route) => {
    try {
      const reqStart = Date.now();
      const url = new URL(route.request().url());
      const targetUrl = `${awsUrl}${url.pathname}${url.search}`;
      const response = await page.request.fetch(targetUrl, {
        method: route.request().method(),
        headers: route.request().headers(),
        data: route.request().postDataBuffer(),
      });
      const duration = Date.now() - reqStart;
      console.log(`[AWS API] ${route.request().method()} ${url.pathname} -> ${response.status()} in ${duration}ms`);
      await route.fulfill({
        response,
      });
    } catch {
      // Ignore errors if context/page closed after assertion completed
    }
  });

  const start = Date.now();
  await page.goto('/', { waitUntil: 'domcontentloaded' });
  const domContentLoadedTime = Date.now() - start;

  await expect(page.locator('header').getByText('Alex Taylor')).toBeVisible();
  const fullyRenderedTime = Date.now() - start;

  console.log(`\n========================================`);
  console.log(`[AWS Lambda Hosting] DOM Content Loaded Time: ${domContentLoadedTime} ms`);
  console.log(`[AWS Lambda Hosting] Fully Rendered Page Time: ${fullyRenderedTime} ms (${(fullyRenderedTime / 1000).toFixed(2)}s)`);
  console.log(`========================================\n`);

  // Target: <1.5s locally, with up to 7.5s allowed in CI to accommodate multi-hop cross-continental API routing
  const threshold = process.env.CI ? 7500 : 3000;
  expect(fullyRenderedTime).toBeLessThan(threshold);
  await page.unrouteAll({ behavior: 'ignoreErrors' });
});