👨🏼‍💻

khriztianmoreno's Blog

Home Tags About |

A Guide to Efficient Web Testing Workflows

Published at 2025-12-07
Updated at 2025-12-07
Licensed under MIT web-testingautomationplaywrightci-cdgoogle-developer

For senior engineers, web testing isn’t about finding bugs—it’s about building a system that prevents them. It’s an architectural concern. This post outlines several key points to consider when designing an industrial-grade testing strategy, with a heavy focus on the tools and protocols at the heart of the Google Chrome ecosystem.

Point 1: Understand the Automation Protocol Landscape

At the lowest level, test automation is about communication with a browser. For years, the dominant standard has been WebDriver, an open W3C standard that Google heavily supports through ChromeDriver. It provides a stable, cross-platform API for browser automation.

// Example using selenium-webdriver with ChromeDriver
import { Builder, By, Key, until } from "selenium-webdriver";

(async function example() {
  let driver = await new Builder().forBrowser("chrome").build();
  try {
    await driver.get("http://www.google.com/ncr");
    await driver.findElement(By.name("q")).sendKeys("webdriver", Key.RETURN);
    await driver.wait(until.titleIs("webdriver - Google Search"), 1000);
  } finally {
    await driver.quit();
  }
})();

However, modern tools often use a more direct, powerful approach: the Chrome DevTools Protocol (CDP). This is the same protocol the Chrome DevTools UI uses to inspect, debug, and profile the browser. It’s faster and offers deeper control than WebDriver, allowing you to do things like intercept network requests, mock sensor data, and trace performance.

Point 2: Leverage the CDP with Modern Tooling like Playwright

Playwright, a project with deep roots in the original Chrome DevTools team, was built from the ground up on the CDP. This is its superpower. By speaking the browser’s native language, it avoids the overhead of WebDriver’s HTTP-based communication, resulting in faster and more reliable tests.

This direct connection enables incredible features like the Playwright Trace Viewer. When a test fails, it generates a trace file that you can open in a browser. It’s a time-traveling debugger for your test, showing you a DOM snapshot, console logs, and network requests for every single action. It’s like having Chrome DevTools open for your CI run.

To make Playwright tests scalable, use the Page Object Model (POM):

LoginPage.ts

import { type Page, type Locator } from "@playwright/test";

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly signInButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel("Email");
    this.passwordInput = page.getByLabel("Password");
    this.signInButton = page.getByRole("button", { name: "Sign in" });
  }

  async login(email: string, pass: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(pass);
    await this.signInButton.click();
  }
}

login.spec.ts

import { test, expect } from "@playwright/test";
import { LoginPage } from "./LoginPage";

test("a user should be able to sign in", async ({ page }) => {
  const loginPage = new LoginPage(page);
  await page.goto("/login");
  await loginPage.login("user@test.com", "password");

  await expect(page).toHaveURL("/dashboard");
});

Point 3: Consider Advanced Testing Vectors

Functional correctness is only one dimension of quality. For a truly robust strategy, consider these vectors:

  • Visual Regression Testing: Tools like Percy integrate with Playwright to take DOM snapshots and render them across different browsers, catching visual bugs that functional tests miss.

    import { test } from "@playwright/test";
    import percySnapshot from "@percy/playwright";
    
    test("the login page should be visually correct", async ({ page }) => {
      await page.goto("/login");
      await percySnapshot(page, "Login Page");
    });
    
  • Consumer-Driven Contract Testing: In a microservices world, use Pact to ensure your frontend and backend can communicate without running expensive, full-scale integration tests. The consumer defines a contract, and the provider must prove it can fulfill it.

Point 4: Make Performance a First-Class Citizen

Performance isn’t something you sprinkle on at the end; it’s a feature. Google has championed this idea with tools like Lighthouse. With Lighthouse CI, you can automate performance auditing and set budgets for your key metrics (LCP, CLS, TBT).

Integrating this into your GitHub Actions workflow is straightforward:

# In your CI workflow, after a deployment step
- name: Run Lighthouse CI
  run: |
    npm install -g @lhci/cli@0.13.x
    lhci autorun
  env:
    LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

This will run Lighthouse against your deployment URLs and fail the build if performance budgets are exceeded, preventing regressions before they ever reach production.

By building a strategy around these points—understanding the protocols, leveraging modern tooling, and expanding your definition of quality—you can create a testing system that enables speed and confidence.

Conclusion: Your Next Step

Don’t try to implement all of this tomorrow. If you’re coming from a legacy environment, my recommendation is clear:

  1. Migrate a critical flow to Playwright and experience the stability of the CDP.
  2. Automate a performance budget with Lighthouse CI to prevent application bloat.
  3. Document the contracts, not just the code.

Quality engineering is not a destination; it’s a continuous discipline. Build systems that don’t just find errors but make them nearly impossible to commit.

Profile

@khriztianmoreno

Until next time.