Update E2E tests and simplify performance tests
CI Pipeline / e2e (chromium) (pull_request) Successful in 6m13s
CI Pipeline / e2e (firefox) (pull_request) Successful in 7m3s
CI Pipeline / e2e (webkit) (pull_request) Successful in 5m52s
CI Pipeline / visual-regression (pull_request) Successful in 7m48s
CI Pipeline / performance (pull_request) Successful in 7m59s
CI Pipeline / lint (pull_request) Successful in 6m16s
CI Pipeline / build (pull_request) Successful in 5m30s
CI Pipeline / test (pull_request) Successful in 6m26s
CI Pipeline / e2e (chromium) (pull_request) Successful in 6m13s
CI Pipeline / e2e (firefox) (pull_request) Successful in 7m3s
CI Pipeline / e2e (webkit) (pull_request) Successful in 5m52s
CI Pipeline / visual-regression (pull_request) Successful in 7m48s
CI Pipeline / performance (pull_request) Successful in 7m59s
CI Pipeline / lint (pull_request) Successful in 6m16s
CI Pipeline / build (pull_request) Successful in 5m30s
CI Pipeline / test (pull_request) Successful in 6m26s
This commit is contained in:
+42
-359
@@ -1,89 +1,66 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { PlaywrightPerformanceMonitor } from "../performance/performance-monitor.js";
|
||||
|
||||
// Environment-aware performance budgets and thresholds
|
||||
// Adjusted for development environment
|
||||
// Performance budgets - simplified for E2E tests
|
||||
// Comprehensive performance testing is handled by Lighthouse CI
|
||||
const PERFORMANCE_BUDGETS = {
|
||||
// Page load performance
|
||||
page_load_time: 4000, // 4 seconds - increased for dev environment
|
||||
first_contentful_paint: 2500, // 2.5 seconds - increased for dev environment
|
||||
largest_contentful_paint: 3000, // 3 seconds - increased for dev environment
|
||||
first_input_delay: 150, // 150ms - increased for dev environment
|
||||
|
||||
// Navigation timing
|
||||
dns_lookup: 100, // 100ms
|
||||
tcp_connection: 200, // 200ms
|
||||
ttfb: 1500, // 1500ms - increased to be more realistic for development environment and mobile
|
||||
dom_content_loaded: 2000, // 2 seconds - increased for dev environment
|
||||
full_load: 4000, // 4 seconds - increased for dev environment
|
||||
|
||||
// Component performance
|
||||
component_render_time: 700, // 700ms - increased for dev environment
|
||||
interaction_time: 1000, // 1000ms - increased for development environment and mobile
|
||||
scroll_performance: process.env.CI ? 250 : 150, // More realistic for dev and mobile (150ms vs 100ms)
|
||||
|
||||
// Resource performance
|
||||
network_request_duration: 1500, // 1.5 seconds - increased for dev environment
|
||||
memory_usage_mb: 60, // 60MB - increased for dev environment
|
||||
};
|
||||
|
||||
// Baseline metrics for regression detection
|
||||
// Adjusted for development environment (more realistic baselines)
|
||||
const BASELINE_METRICS = {
|
||||
page_load_time: 2500, // Increased from 2000ms
|
||||
first_contentful_paint: 1800, // Increased from 1500ms
|
||||
largest_contentful_paint: 2200, // Increased from 2000ms
|
||||
first_input_delay: 80, // Increased from 50ms
|
||||
dns_lookup: 50,
|
||||
tcp_connection: 100,
|
||||
ttfb: 600, // Increased from 400ms to be more realistic for dev
|
||||
dom_content_loaded: 1200, // Increased from 1000ms
|
||||
full_load: 2500, // Increased from 2000ms
|
||||
component_render_time: 400, // Increased from 300ms
|
||||
interaction_time: 200, // Increased from 100ms to be more realistic for mobile
|
||||
scroll_performance: 100, // Increased from 60ms to be more realistic for mobile
|
||||
network_request_duration: 700, // Increased from 500ms
|
||||
memory_usage_mb: 40, // Increased from 30MB
|
||||
page_load_time: 4000, // 4 seconds
|
||||
first_contentful_paint: 2500, // 2.5 seconds
|
||||
largest_contentful_paint: 3000, // 3 seconds
|
||||
first_input_delay: 150, // 150ms
|
||||
ttfb: 1500, // 1.5 seconds
|
||||
dom_content_loaded: 2000, // 2 seconds
|
||||
full_load: 4000, // 4 seconds
|
||||
};
|
||||
|
||||
test.describe("Performance Monitoring", () => {
|
||||
let performanceMonitor: PlaywrightPerformanceMonitor;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Mark tests as slower in CI environment
|
||||
test.beforeEach(async () => {
|
||||
if (process.env.CI) test.slow();
|
||||
|
||||
performanceMonitor = new PlaywrightPerformanceMonitor(page);
|
||||
performanceMonitor.setThresholds(PERFORMANCE_BUDGETS);
|
||||
performanceMonitor.setBaselines(BASELINE_METRICS);
|
||||
});
|
||||
|
||||
test("homepage load performance", async ({ page: _page }) => {
|
||||
const result = await performanceMonitor.measurePageLoad("/");
|
||||
test("homepage load performance", async ({ page }) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Navigate to homepage
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
// Get performance metrics from browser
|
||||
const metrics = await page.evaluate(() => {
|
||||
const navigation = performance.getEntriesByType("navigation")[0];
|
||||
const paint = performance.getEntriesByType("paint");
|
||||
|
||||
return {
|
||||
ttfb: navigation?.responseStart - navigation?.requestStart || 0,
|
||||
domContentLoaded:
|
||||
navigation?.domContentLoadedEventEnd -
|
||||
navigation?.domContentLoadedEventStart || 0,
|
||||
load: navigation?.loadEventEnd - navigation?.loadEventStart || 0,
|
||||
firstContentfulPaint:
|
||||
paint.find((p) => p.name === "first-contentful-paint")?.startTime ||
|
||||
0,
|
||||
};
|
||||
});
|
||||
|
||||
// Assert page load time is within budget
|
||||
expect(result.loadTime).toBeLessThan(PERFORMANCE_BUDGETS.page_load_time);
|
||||
expect(loadTime).toBeLessThan(PERFORMANCE_BUDGETS.page_load_time);
|
||||
|
||||
// Assert individual metrics
|
||||
expect(result.metrics.ttfb).toBeLessThan(PERFORMANCE_BUDGETS.ttfb);
|
||||
expect(result.metrics.domContentLoaded).toBeLessThan(
|
||||
expect(metrics.ttfb).toBeLessThan(PERFORMANCE_BUDGETS.ttfb);
|
||||
expect(metrics.domContentLoaded).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.dom_content_loaded,
|
||||
);
|
||||
expect(result.metrics.load).toBeLessThan(PERFORMANCE_BUDGETS.full_load);
|
||||
|
||||
// Check for performance regressions
|
||||
const summary = performanceMonitor.getSummary();
|
||||
console.log("Performance Summary:", summary);
|
||||
expect(metrics.load).toBeLessThan(PERFORMANCE_BUDGETS.full_load);
|
||||
expect(metrics.firstContentfulPaint).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.first_contentful_paint,
|
||||
);
|
||||
});
|
||||
|
||||
test("core web vitals", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Wait for page to fully load
|
||||
// Use "load" state instead of "networkidle" to handle dynamically imported components
|
||||
await page.waitForLoadState("load");
|
||||
|
||||
// Get Core Web Vitals with timeout
|
||||
// Get Core Web Vitals using browser Performance API
|
||||
const coreWebVitals = (await page.evaluate(() => {
|
||||
return new Promise<{ lcp: number; fid: number; cls: number }>(
|
||||
(resolve) => {
|
||||
@@ -145,298 +122,4 @@ test.describe("Performance Monitoring", () => {
|
||||
);
|
||||
expect(coreWebVitals.cls).toBeLessThan(0.1); // CLS should be less than 0.1
|
||||
});
|
||||
|
||||
test("component render performance", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Measure header render time
|
||||
const headerRenderTime =
|
||||
await performanceMonitor.measureComponentRender("header");
|
||||
expect(headerRenderTime).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.component_render_time,
|
||||
);
|
||||
|
||||
// Measure footer render time
|
||||
const footerRenderTime =
|
||||
await performanceMonitor.measureComponentRender("footer");
|
||||
expect(footerRenderTime).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.component_render_time,
|
||||
);
|
||||
|
||||
// Measure main content render time
|
||||
const mainRenderTime =
|
||||
await performanceMonitor.measureComponentRender("main");
|
||||
expect(mainRenderTime).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.component_render_time,
|
||||
);
|
||||
});
|
||||
|
||||
test("interaction performance", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Wait for page to be ready
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForTimeout(1000); // Give page time to stabilize
|
||||
|
||||
// Measure button click performance with better element selection
|
||||
const buttonClickTime = await performanceMonitor.measureInteraction(
|
||||
'button:has-text("Learn how CommunityRule works")',
|
||||
async () => {
|
||||
const learnButtons = page.locator(
|
||||
'button:has-text("Learn how CommunityRule works")',
|
||||
);
|
||||
const buttonCount = await learnButtons.count();
|
||||
let visibleButton = null;
|
||||
|
||||
for (let i = 0; i < buttonCount; i++) {
|
||||
const button = learnButtons.nth(i);
|
||||
if (await button.isVisible()) {
|
||||
visibleButton = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!visibleButton) {
|
||||
// Skip this test if button is not visible (might be hidden on some viewports)
|
||||
console.log("Button not visible, skipping button click test");
|
||||
return;
|
||||
}
|
||||
|
||||
await visibleButton.click();
|
||||
},
|
||||
);
|
||||
|
||||
if (buttonClickTime !== null) {
|
||||
expect(buttonClickTime).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.interaction_time,
|
||||
);
|
||||
}
|
||||
|
||||
// Measure link click performance with better element selection
|
||||
const linkClickTime = await performanceMonitor.measureInteraction(
|
||||
'a:has-text("Use cases")',
|
||||
async () => {
|
||||
const useCaseLinks = page.locator('a:has-text("Use cases")');
|
||||
const linkCount = await useCaseLinks.count();
|
||||
let visibleLink = null;
|
||||
|
||||
for (let i = 0; i < linkCount; i++) {
|
||||
const link = useCaseLinks.nth(i);
|
||||
if (await link.isVisible()) {
|
||||
visibleLink = link;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!visibleLink) {
|
||||
// Skip this test if link is not visible
|
||||
console.log("Link not visible, skipping link click test");
|
||||
return;
|
||||
}
|
||||
|
||||
await visibleLink.click();
|
||||
},
|
||||
);
|
||||
|
||||
if (linkClickTime !== null) {
|
||||
expect(linkClickTime).toBeLessThan(PERFORMANCE_BUDGETS.interaction_time);
|
||||
}
|
||||
});
|
||||
|
||||
test("scroll performance", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Measure scroll performance
|
||||
const scrollTime = await performanceMonitor.measureScrollPerformance();
|
||||
expect(scrollTime).toBeLessThan(PERFORMANCE_BUDGETS.scroll_performance);
|
||||
});
|
||||
|
||||
test("memory usage", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Get memory usage
|
||||
const memoryUsage = await performanceMonitor.getMemoryUsage();
|
||||
|
||||
if (memoryUsage) {
|
||||
const usedMemoryMB = memoryUsage.usedJSHeapSize / 1024 / 1024;
|
||||
expect(usedMemoryMB).toBeLessThan(PERFORMANCE_BUDGETS.memory_usage_mb);
|
||||
|
||||
console.log(`Memory Usage: ${usedMemoryMB.toFixed(2)}MB`);
|
||||
}
|
||||
});
|
||||
|
||||
test("network request performance", async ({ page }) => {
|
||||
await performanceMonitor.monitorNetworkRequests();
|
||||
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
// Wait for load state instead of networkidle to handle dynamic imports
|
||||
await page.waitForLoadState("load");
|
||||
|
||||
// Check that all requests completed within budget
|
||||
const summary = performanceMonitor.getSummary();
|
||||
if (summary.network_request_duration) {
|
||||
expect(summary.network_request_duration.average).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.network_request_duration,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("responsive performance across breakpoints", async ({ page }) => {
|
||||
const breakpoints = [
|
||||
{ name: "mobile", width: 375, height: 667 },
|
||||
{ name: "tablet", width: 768, height: 1024 },
|
||||
{ name: "desktop", width: 1280, height: 720 },
|
||||
];
|
||||
|
||||
for (const breakpoint of breakpoints) {
|
||||
await page.setViewportSize(breakpoint);
|
||||
|
||||
const result = await performanceMonitor.measurePageLoad("/");
|
||||
|
||||
// Assert performance is maintained across breakpoints
|
||||
expect(result.loadTime).toBeLessThan(PERFORMANCE_BUDGETS.page_load_time);
|
||||
|
||||
console.log(`${breakpoint.name} load time: ${result.loadTime}ms`);
|
||||
}
|
||||
});
|
||||
|
||||
test("performance under load", async ({ page }) => {
|
||||
// Simulate slower network conditions
|
||||
await page.route("**/*", (route) => {
|
||||
route.continue();
|
||||
});
|
||||
|
||||
// Add artificial delay to simulate network latency
|
||||
await page.addInitScript(() => {
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = async (...args) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms delay
|
||||
return originalFetch(...args);
|
||||
};
|
||||
});
|
||||
|
||||
const result = await performanceMonitor.measurePageLoad("/");
|
||||
|
||||
// Even under load, page should load within reasonable time
|
||||
expect(result.loadTime).toBeLessThan(
|
||||
PERFORMANCE_BUDGETS.page_load_time * 1.5,
|
||||
);
|
||||
});
|
||||
|
||||
test("performance regression detection", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Simulate a performance regression by adding a heavy operation
|
||||
await page.addInitScript(() => {
|
||||
// Add a heavy operation that would cause regression
|
||||
const heavyOperation = () => {
|
||||
let result = 0;
|
||||
for (let i = 0; i < 1000000; i++) {
|
||||
result += Math.random();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Execute heavy operation on page load
|
||||
window.addEventListener("load", () => {
|
||||
heavyOperation();
|
||||
});
|
||||
});
|
||||
|
||||
await performanceMonitor.measurePageLoad("/");
|
||||
|
||||
// This should trigger a performance regression warning
|
||||
const summary = performanceMonitor.getSummary();
|
||||
console.log("Performance Summary with Regression:", summary);
|
||||
});
|
||||
|
||||
test("performance metrics export", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Perform various operations to collect metrics
|
||||
await performanceMonitor.measureComponentRender("header");
|
||||
await performanceMonitor.measureScrollPerformance();
|
||||
await performanceMonitor.getMemoryUsage();
|
||||
|
||||
// Export all metrics
|
||||
const exportedData = performanceMonitor.export();
|
||||
|
||||
// Verify exported data structure
|
||||
expect(exportedData.metrics).toBeDefined();
|
||||
expect(exportedData.baselines).toBeDefined();
|
||||
expect(exportedData.thresholds).toBeDefined();
|
||||
expect(exportedData.summary).toBeDefined();
|
||||
|
||||
console.log(
|
||||
"Exported Performance Data:",
|
||||
JSON.stringify(exportedData, null, 2),
|
||||
);
|
||||
});
|
||||
|
||||
test("performance budget compliance", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load", timeout: 60000 });
|
||||
|
||||
// Collect comprehensive metrics
|
||||
await performanceMonitor.measurePageLoad("/");
|
||||
await performanceMonitor.measureComponentRender("header");
|
||||
await performanceMonitor.measureComponentRender("footer");
|
||||
await performanceMonitor.measureScrollPerformance();
|
||||
await performanceMonitor.getMemoryUsage();
|
||||
|
||||
const summary = performanceMonitor.getSummary();
|
||||
|
||||
// Check all metrics against budgets
|
||||
for (const [metricName, budget] of Object.entries(PERFORMANCE_BUDGETS)) {
|
||||
if (summary[metricName]) {
|
||||
const actualValue =
|
||||
summary[metricName].latest || summary[metricName].average;
|
||||
expect(actualValue).toBeLessThan(budget);
|
||||
console.log(`${metricName}: ${actualValue}ms (budget: ${budget}ms)`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Performance Regression Testing", () => {
|
||||
test("detect performance regressions over time", async ({ page }) => {
|
||||
const performanceMonitor = new PlaywrightPerformanceMonitor(page);
|
||||
|
||||
// Set strict baselines for regression detection
|
||||
const strictBaselines = {
|
||||
page_load_time: 1500,
|
||||
first_contentful_paint: 1000,
|
||||
component_render_time: 200,
|
||||
interaction_time: 30,
|
||||
};
|
||||
|
||||
performanceMonitor.setBaselines(strictBaselines);
|
||||
|
||||
// Run multiple iterations to detect trends
|
||||
const iterations = 3;
|
||||
const results = [];
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
// measurePageLoad already handles timeouts and wait conditions
|
||||
const result = await performanceMonitor.measurePageLoad("/");
|
||||
results.push(result.loadTime);
|
||||
|
||||
// Small delay between iterations
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
// Check for consistent performance
|
||||
const averageLoadTime = results.reduce((a, b) => a + b, 0) / results.length;
|
||||
const variance =
|
||||
results.reduce(
|
||||
(acc, val) => acc + Math.pow(val - averageLoadTime, 2),
|
||||
0,
|
||||
) / results.length;
|
||||
|
||||
// Performance should be consistent (low variance)
|
||||
// Increased threshold for development environment which has more variability
|
||||
expect(variance).toBeLessThan(600000); // Variance should be less than 600ms² for dev environment
|
||||
|
||||
console.log(`Average load time: ${averageLoadTime}ms`);
|
||||
console.log(`Variance: ${variance}`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user