Back to Blog

Non-Functional Testing with Playwright: Cover Performance, Security, and Accessibility in One Suite

Explore the world of non-functional testing with Playwright. Learn how to conduct performance, accessibility, security, and load testing to build truly robust web applications.

ScanlyApp Team

QA Testing and Automation Experts

Published

10 min read

Reading time

Non-Functional Testing with Playwright: Cover Performance, Security, and Accessibility in One Suite

You’ve done it. Your team has built a beautiful web application with a suite of functional tests that guarantee every button click, form submission, and user flow works exactly as designed. Your functional test suite is green. Time to ship, right?

Not so fast.

A user logs in, and the page takes 10 seconds to load. Another user, who relies on a screen reader, can't navigate your "perfect" UI. A sudden spike in traffic during a marketing campaign brings your server to its knees.

This is the critical gap that non-functional testing fills. While functional testing asks, "Does it work?", non-functional testing asks, "How well does it work?" For founders, builders, and even no-code testers, understanding this distinction is the key to moving from a product that merely works to a product that users love.

Playwright, known for its powerful functional testing capabilities, is also an exceptional tool for conducting various types of non-functional testing. This guide will show you how to leverage Playwright to go beyond basic checks and validate the performance, accessibility, security, and load-bearing capacity of your application.

What is Non-Functional Testing (and Why Should You Care)?

Non-functional testing (NFT) is a type of software testing that evaluates the non-functional aspects of an application, such as its performance, reliability, usability, and security. It's the difference between a house that has all its rooms and a house that's comfortable, safe, and can withstand a storm.

Aspect Functional Testing (Does it work?) Non-Functional Testing (How well does it work?)
Login Can a user log in with valid credentials? How quickly does the login process complete? Can 1,000 users log in simultaneously?
Image Upload Can a user upload a JPG file? How does the app handle a 50MB image upload? Is the upload process secure from malicious files?
Dashboard Does the dashboard display the correct data? How long does the dashboard take to load? Is the dashboard usable for someone with visual impairments?
UI Do all buttons and links work? Is the layout intuitive? Does the UI break on different screen sizes?

For any business, ignoring non-functional testing is a direct path to poor user experience, reputational damage, and lost revenue.

1. Performance Testing with Playwright

Performance testing isn't just for specialized engineers anymore. With Playwright, any developer or tester can get immediate feedback on how their changes impact application speed.

Key Performance Metrics to Track

  • Time to First Byte (TTFB): How long it takes for the server to start responding.
  • First Contentful Paint (FCP): When the first piece of content is rendered on the screen.
  • Largest Contentful Paint (LCP): When the largest content element becomes visible. A key Core Web Vital.
  • Page Load Time: The total time it takes for the page and all its resources to load.
  • API Response Times: How quickly your backend services respond to requests.

How to Measure Performance with Playwright

Playwright provides built-in tools to capture these metrics.

import { test, expect } from '@playwright/test';

test.describe('Homepage Performance', () => {
  test('should load within performance budget', async ({ page }) => {
    const response = await page.goto('/');

    // Check TTFB from the server response timing
    const timing = response.timing();
    const ttfb = timing.responseStart - timing.requestStart;
    console.log(`Time to First Byte: ${ttfb.toFixed(2)}ms`);
    expect(ttfb).toBeLessThan(500); // Set your budget: 500ms

    // Use the Performance API in the browser context
    const performanceMetrics = await page.evaluate(() => {
      const paintTimings = performance.getEntriesByType('paint');
      const navigationTiming = performance.getEntriesByType('navigation')[0];

      const fcp = paintTimings.find((p) => p.name === 'first-contentful-paint')?.startTime;
      const lcp = performance.getEntriesByType('largest-contentful-paint').pop()?.startTime;
      const pageLoad = navigationTiming.loadEventEnd;

      return { fcp, lcp, pageLoad };
    });

    console.log(`First Contentful Paint: ${performanceMetrics.fcp.toFixed(2)}ms`);
    console.log(`Largest Contentful Paint: ${performanceMetrics.lcp.toFixed(2)}ms`);
    console.log(`Page Load Time: ${performanceMetrics.pageLoad.toFixed(2)}ms`);

    // Assert against your performance budget
    expect(performanceMetrics.fcp).toBeLessThan(1800); // Google's recommendation
    expect(performanceMetrics.lcp).toBeLessThan(2500); // Core Web Vital threshold
    expect(performanceMetrics.pageLoad).toBeLessThan(3000);
  });

  test('should measure API response times', async ({ page }) => {
    // Listen for a specific API call
    const apiPromise = page.waitForResponse('**/api/projects');

    await page.goto('/dashboard');

    const apiResponse = await apiPromise;
    const duration = apiResponse.timing().responseEnd - apiResponse.timing().requestStart;

    console.log(`Projects API response time: ${duration.toFixed(2)}ms`);
    expect(duration).toBeLessThan(400); // Your API budget
  });
});

Pro Tip for Founders: You don't need to understand the code to set the goals. Tell your team, "Our login page's LCP must be under 2 seconds." They can then use scripts like the one above to ensure that goal is met in every deployment.

2. Accessibility (a11y) Testing with Playwright

An accessible website is one that can be used by everyone, including people with disabilities. This isn't just a "nice-to-have"; it's a legal and ethical requirement that also expands your market reach.

Common Accessibility Issues

  • Missing alt text for images.
  • Poor color contrast.
  • Non-descriptive links like "Click Here."
  • Forms without proper labels.
  • UI elements that aren't keyboard-navigable.

Automated Accessibility Scanning

Playwright can be integrated with accessibility testing libraries like axe-core to automatically scan for violations.

Step 1: Install axe-playwright

npm install --save-dev axe-playwright

Step 2: Create Your Accessibility Test

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility Checks', () => {
  test('homepage should not have any automatically detectable accessibility issues', async ({ page }) => {
    await page.goto('/');

    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) // Define standards
      .analyze();

    // Log violations for debugging
    if (accessibilityScanResults.violations.length > 0) {
      console.log('Accessibility violations found:');
      console.log(accessibilityScanResults.violations);
    }

    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('login form should be accessible', async ({ page }) => {
    await page.goto('/login');

    // You can also scan specific components
    const accessibilityScanResults = await new AxeBuilder({ page })
      .include('form') // Target the form element
      .analyze();

    expect(accessibilityScanResults.violations).toEqual([]);
  });
});

This simple test can catch dozens of common issues, ensuring your site is usable by a wider audience. For a deeper dive, check out our full guide on accessibility testing.

3. Security Testing with Playwright

While Playwright is not a dedicated penetration testing tool, it can be used to automate checks for common security vulnerabilities from a user's perspective. This is a key part of a "shift-left" security strategy.

Basic Security Checks to Automate

  • Check for Secure Cookies: Ensure sensitive cookies have Secure, HttpOnly, and SameSite attributes.
  • Verify Headers: Check for security headers like Content-Security-Policy and X-Frame-Options.
  • Test Authentication Flows: Ensure logging out properly invalidates the session.
  • Detect Exposed Information: Scan the page source for accidentally exposed API keys or sensitive comments.

Example Security Checks

import { test, expect } from '@playwright/test';

test.describe('Basic Security Scans', () => {
  test('should have secure headers on the homepage', async ({ page }) => {
    const response = await page.goto('/');
    const headers = response.headers();

    // Content-Security-Policy: Prevents XSS attacks
    expect(headers['content-security-policy']).toBeDefined();
    expect(headers['content-security-policy']).toContain("script-src 'self'");

    // X-Frame-Options: Prevents clickjacking
    expect(headers['x-frame-options']).toBe('DENY');

    // X-Content-Type-Options: Prevents MIME-sniffing
    expect(headers['x-content-type-options']).toBe('nosniff');
  });

  test('session cookie should be secure', async ({ page, context }) => {
    // Log in to create a session
    await page.goto('/login');
    await page.fill('input[name="email"]', 'test@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    await page.waitForURL('/dashboard');

    // Get cookies for the context
    const cookies = await context.cookies();
    const sessionCookie = cookies.find((c) => c.name === 'session_id');

    expect(sessionCookie).toBeDefined();
    expect(sessionCookie.httpOnly).toBe(true); // Not accessible via JavaScript
    expect(sessionCookie.secure).toBe(true); // Only sent over HTTPS
    expect(sessionCookie.sameSite).toBe('Lax'); // Mitigates CSRF
  });

  test('should not expose sensitive information in the page source', async ({ page }) => {
    await page.goto('/');
    const pageContent = await page.content();

    // Regex to find patterns like `sk_live_...` or `API_KEY=...`
    const sensitivePattern = /sk_live_[a-zA-Z0-9]+|API_KEY\s*=\s*['"][^'"]+['"]/i;

    expect(pageContent).not.toMatch(sensitivePattern);
  });
});

4. Load Testing with Playwright and Artillery

This is where things get exciting. While Playwright itself runs a single browser instance, you can combine it with load testing tools to simulate thousands of users interacting with your site. This is crucial for any founder who expects their app to go viral.

We'll use Artillery, a modern load testing tool that has experimental support for Playwright.

Step 1: Install Artillery and the Playwright Engine

npm install -g artillery
npm install @artilleryio/engine-playwright

Step 2: Create an Artillery Test Script

This script defines a user journey in Playwright and then tells Artillery how many virtual users should execute it.

load-test.yml:

config:
  target: 'https://app.scanlyapp.com' # Your staging or prod URL
  phases:
    - duration: 60 # Ramp up to 20 virtual users over 60 seconds
      arrivalRate: 20
    - duration: 300 # Stay at 20 users/sec for 5 minutes
      arrivalRate: 20
  engines:
    playwright: {} # Use the Playwright engine

scenarios:
  - name: 'User logs in and views dashboard'
    engine: playwright
    flow:
      - launch: # Launch a browser
          headless: true
      - goto: '/login'
      - fill:
          - selector: "input[name='email']"
            value: '{{ email }}' # Use variables from a CSV
          - selector: "input[name='password']"
            value: '{{ password }}'
      - click:
          - selector: "button[type='submit']"
      - waitForNavigation
      - think: 5 # Pause for 5 seconds to simulate reading
      - close # Close the browser

Step 3: Run the Load Test

artillery run load-test.yml

Artillery will output a report showing response times, success rates, and other metrics under load. This helps you answer questions like:

  • Can our infrastructure handle 100 concurrent users?
  • At what point does performance start to degrade?
  • Are there bottlenecks in our API or database under stress?

Integrating Non-Functional Testing into Your Workflow

The key to success is automation. These tests should not be run manually; they should be an integral part of your CI/CD pipeline.

A Modern CI/CD Workflow with NFT

graph TD
    A[Developer Pushes Code] --> B{CI Pipeline Triggered};
    B --> C[Run Unit & Integration Tests];
    C --> D{Build & Deploy to Staging};
    D --> E[Run Functional E2E Tests];
    E --> F[Run Non-Functional Tests];
    F --> G{All Tests Pass?};
    G -- Yes --> H[Deploy to Production];
    G -- No --> I[Alert Developer & Block Deploy];

    subgraph Non-Functional Suite
        F1[Performance Budget Check]
        F2[Accessibility Scan]
        F3[Basic Security Checks]
    end

    F --> F1;
    F --> F2;
    F --> F3;

By integrating these checks, you create a quality gate that prevents performance regressions, accessibility issues, and basic security flaws from ever reaching production. This aligns perfectly with a continuous testing strategy.

Your Path to a Truly High-Quality Application

Functional testing is the foundation, but non-functional testing is what makes your application exceptional. By expanding your test suite to include performance, accessibility, security, and load testing, you build a product that is not only correct but also fast, inclusive, secure, and reliable.

For builders and founders, this means happier users, better retention, and a stronger brand. For testers and developers, it means confidence in every release.

Ready to Automate Your Quality Assurance?

Manually running these checks is time-consuming and error-prone. A comprehensive QA platform can automate this entire process for you.

ScanlyApp provides a no-code solution for:Automated E2E Testing: Ensure your user flows work flawlessly. ✅ Performance Monitoring: Get alerts when your app slows down. ✅ Visual Regression Testing: Catch UI bugs before your users do. ✅ Continuous CI/CD Integration: Build a quality gate without writing complex scripts.

Stop worrying about what might break. Start your free ScanlyApp trial today and ship with confidence.

Related Posts