View all services
Talk to QA Advisor
/Blog/Web Application Testing (2027): Process, Checklist and a Real Test Run
Functional Testing9 min read

Web Application Testing (2027): Process, Checklist and a Real Test Run

The web application testing process in seven steps, a browser matrix built from market data, a checklist with pass criteria, and a real run on five browser targets with every failure read.

Published February 6, 2023Last updated October 28, 2026
On this page

Web application testing checks that a browser-based application does what its users and its owner need, on the browsers, devices and networks those users actually have. That covers seven areas: functional behaviour, content, layout, cross-browser behaviour, accessibility, security and performance. This guide covers the process we follow for web application testing.

Most guides list those areas and stop. This one also runs them. We wrote ten Playwright checks, pointed them at a public practice application built to misbehave, and ran them on five browser targets. Twelve of the fifty results failed. Reading those failures teaches more about web application testing than any list of testing types, because three of the twelve were not defects at all.

You will get the process in seven steps, a browser matrix built from market data, the code we ran, every result, a checklist table and a release gate.

Start from risk and traffic, not from a tool

A test plan built around a tool tests what the tool is good at. A plan built around risk tests what would hurt the business if it broke. Start with two lists before you write a single test.

List the flows that make or lose money

Write down every flow where a defect costs money, data or trust. For most web applications that means sign-up and login, the core task the product exists for, payment or checkout, and anything that changes stored data. Add every user role, because an admin and a customer see different screens and break in different ways.

Rank the list by what a failure would cost. A broken password reset locks customers out. A misaligned footer annoys nobody. The ranking decides where your deepest testing goes.

Build the browser matrix from data, not habit

Test the rendering engine, not the brand name. Chrome, Edge, Samsung Internet and Opera all run on Chromium. Safari runs on WebKit, and every browser on an iPhone has historically used WebKit too. Firefox runs on Gecko.

Figure 1 · Browser matrix
Six browsers, three engines
Test the engine, not the brand name: Edge, Samsung Internet and Opera all run on Chromium.
Chrome
Engine: Chromium
66.36%
Safari
Engine: WebKit
18.27%
Edge
Engine: Chromium
6.04%
Firefox
Engine: Gecko
2.80%
Samsung Internet
Engine: Chromium
2.31%
Opera
Engine: Chromium
1.77%
Three engines cover all six
Chromium: 76.48%
WebKit: 18.27%
Gecko: 2.80%
Source: StatCounter, worldwide browser market share, September 2026. Engine totals are the sums of the shares shown. Mobile is 58.99% of page views, desktop 39.26%, tablet 1.76%.

Three engines cover all six browsers in the table. That makes the minimum matrix simple: one Chromium browser, WebKit and Gecko. Then add the phones. Mobile accounts for 58.99% of page views worldwide, so at least one Android and one iPhone profile belong in every run.

Your own analytics beat world averages. A B2B tool used inside large companies may see far more Edge, and an Indian consumer app far more Android. Pull the last 90 days of browser and device data before you fix the matrix.

The web application testing process, step by step

The steps below are the ones we follow on client engagements. Steps four to six repeat for every build until someone makes the release decision.

Figure 2 · The process
Seven steps, in this order
Steps 4 to 6 repeat for every build until the release decision.
1
Analyse
List the flows that make or lose money, the user roles, and the browsers your traffic uses.
2
Plan
One line per risk: what you will test, how, on which environment, and what counts as a pass.
3
Prepare
A stable test environment, seeded accounts for every role, and data you can reset.
4
Execute
Run the automated suite on every build; run exploratory sessions on new or changed features.
5
Report
Each defect with steps, expected and actual result, browser, build and severity.
6
Retest and regress
Confirm each fix, then re-run the suite to catch what the fix broke.
7
Close
Record what was tested, what was not, and the risks you are releasing with.
QAble's process; the service page groups it as Analyse, Plan, Execute, Report, Close.

Analyse and plan

Analysis turns the risk list into test conditions. Each condition gets one line in the plan: what you will test, how, on which environment, and what counts as a pass. A plan without pass criteria produces arguments at release time, because nobody agreed what "working" meant.

Prepare the environment and the data

Most flaky web tests trace back to the environment, not the code. You need a stable environment that matches production configuration, accounts seeded for every role, and data you can reset to a known state. Shared test accounts that other people change mid-run are a common cause of false failures.

Execute, report and retest

Run the automated suite on every build. Spend the human time on exploratory sessions against new or changed features, where scripted tests have nothing to say yet. Every defect report needs steps, the expected and actual result, the browser, the build and a severity. After each fix, retest the defect and then re-run the regression suite, because fixes break neighbouring code. Our guide to choosing what to re-run covers that trade-off in detail.

Close with a written record

Closure is a short document: what was tested, what was not, and which known risks the release carries. It protects the team when a defect appears later in an area that was explicitly out of scope.

Seven areas every web application test plan covers

Each area fails in a different way, so each needs its own check. The right column of the figure below shows what our run found in each one.

Figure 3 · Coverage
Seven areas every test plan needs
Each one fails in a different way, so each needs its own check.
Functional
Every flow does what the requirement says, for every role.
Our run: Login, wrong password, late-loading content
Content integrity
Every image renders and every link resolves.
Our run: 2 of 3 images broken
Layout
No horizontal scroll, nothing clipped, at each viewport.
Our run: Passed at 1,280, 412 and 393 px
Cross-browser
The same results on Chromium, WebKit and Gecko.
Our run: Same 23 passes on all five targets
Accessibility
WCAG 2.2 A and AA, automated scan plus a manual pass.
Our run: 0 violations, 8 elements undecided
Security
Headers, cookies, sessions, inputs, access control.
Our run: 4 of 6 baseline headers missing
Performance
Core Web Vitals at the 75th percentile, and load.
Our run: LCP 1,628 to 2,316 ms in the lab
Run results from our Playwright 1.64.1 suite against the-internet.herokuapp.com, 28 October 2026.

Functional behaviour

Functional testing confirms every flow does what the requirement says, for every role. Test the happy path, then the failures: wrong password, expired session, empty form, duplicate submission, the back button in the middle of a multi-step flow. Error paths are where most functional defects live, because developers test the path they built.

Content integrity

Content integrity means every image renders and every link resolves. It sounds trivial, and it is the first thing a visitor notices when it fails. It is also easy to automate, which makes it a poor use of a tester's time by hand.

Layout and responsiveness

Check each key page at a small phone width, a large phone and a desktop. The cheapest useful automated check is whether the page scrolls sideways: if the document is wider than the viewport, something is overflowing.

Cross-browser behaviour

Cross-browser testing confirms the same results on Chromium, WebKit and Gecko. Run the same suite on each engine rather than separate suites, so a difference in results points straight at an engine difference.

Accessibility

Test against WCAG 2.2, levels A and AA. The W3C published WCAG 2.2 as a Recommendation and "advises the use of WCAG 2.2 to maximize future applicability of accessibility efforts". Automated scanners find part of the problem. A manual pass with a keyboard and a screen reader finds the rest. Our guide to Playwright accessibility testing shows the automated half in more depth, and the WCAG compliance audit guide covers buying a full audit.

Security

The OWASP Web Security Testing Guide is the reference most testers work from. Its stable release is version 4.2, and version 5.0 is in development. A functional tester can cover the baseline: security headers, cookie flags, session expiry, access control between roles, and input handling. Deeper work belongs in a penetration test of the web application. Pentesting without the owner's written permission can be illegal, for example under the UK Computer Misuse Act 1990. That permission, and an agreed scope, are what separate a pentester from a hacker.

Performance

Google's Core Web Vitals set three targets: Largest Contentful Paint within 2.5 seconds, Interaction to Next Paint of 200 milliseconds or less, and Cumulative Layout Shift of 0.1 or less. web.dev measures them "at the 75th percentile of page loads, segmented across mobile and desktop devices". Load testing, which asks how the application behaves under many users at once, is a separate exercise with its own tools.

A real run: ten checks, five browser targets

To show what these areas look like in practice, we ran them. The application under test is the-internet.herokuapp.com, a practice site owned by Sauce Labs. Its own README describes it as "an example application that captures prominent and ugly functionality found on the web". In other words, some of its defects are deliberate.

The setup

We used Playwright 1.64.1 and axe-core 4.14.0. One configuration file defines five targets: desktop Chrome, desktop Firefox, desktop Safari through WebKit, a Pixel 7 profile and an iPhone 15 profile.

typescript
import { defineConfig, devices } from '@playwright/test';

// One spec set, five browser targets. Desktop share and mobile share in the
// article come from StatCounter (Sept 2026), not from this file.
export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: 0,
  reporter: [['list'], ['json', { outputFile: 'results.json' }]],
  use: { baseURL: 'https://the-internet.herokuapp.com', actionTimeout: 5_000 },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 15'] } },
  ],
});

The checks

Here is the spec file exactly as we ran it. Checks that do not depend on the browser engine, such as response headers, run once on Chromium and skip elsewhere.

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

// Web application checks against https://the-internet.herokuapp.com, a practice
// app that ships deliberate defects. Some tests are written to fail: the post
// reports what each one found.

test.describe('functional', () => {
  test('valid login reaches the secure area', async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Username').fill('tomsmith');
    await page.getByLabel('Password').fill('SuperSecretPassword!');
    await page.getByRole('button', { name: 'Login' }).click();
    await expect(page).toHaveURL(/\/secure$/);
    await expect(page.locator('#flash')).toContainText('You logged into a secure area!');
  });

  test('wrong password is rejected with a message', async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Username').fill('tomsmith');
    await page.getByLabel('Password').fill('wrong-password');
    await page.getByRole('button', { name: 'Login' }).click();
    await expect(page).toHaveURL(/\/login$/);
    await expect(page.locator('#flash')).toContainText('Your password is invalid!');
  });

  test('content that loads late is waited for, not slept for', async ({ page }) => {
    await page.goto('/dynamic_loading/2');
    await page.getByRole('button', { name: 'Start' }).click();
    await expect(page.locator('#finish')).toHaveText('Hello World!', { timeout: 10_000 });
  });
});

test.describe('content integrity', () => {
  test('every image on the page actually renders', async ({ page }) => {
    await page.goto('/broken_images');
    const imgs = page.locator('.example img');
    const n = await imgs.count();
    const broken: string[] = [];
    for (let i = 0; i < n; i++) {
      const ok = await imgs.nth(i).evaluate((el: HTMLImageElement) => el.complete && el.naturalWidth > 0);
      if (!ok) broken.push(await imgs.nth(i).getAttribute('src') ?? '?');
    }
    console.log(`IMAGES ${n} checked, broken: ${JSON.stringify(broken)}`);
    expect(broken, 'broken images').toEqual([]);
  });

  test('every link on the home page resolves without a 4xx or 5xx', async ({ page, request }) => {
    await page.goto('/');
    const hrefs = await page.locator('a[href]').evaluateAll(as => as.map(a => (a as HTMLAnchorElement).href));
    const internal = [...new Set(hrefs.filter(h => h.startsWith('https://the-internet.herokuapp.com')))];
    const bad: string[] = [];
    for (const h of internal) {
      const r = await request.get(h, { maxRedirects: 5, failOnStatusCode: false });
      if (r.status() >= 400) bad.push(`${r.status()} ${h.replace('https://the-internet.herokuapp.com', '')}`);
    }
    console.log(`LINKS ${internal.length} internal links checked, failing: ${JSON.stringify(bad)}`);
    expect(bad, 'links returning 4xx/5xx').toEqual([]);
  });
});

test.describe('layout', () => {
  test('login page has no horizontal scroll at this viewport', async ({ page }) => {
    await page.goto('/login');
    const { sw, cw } = await page.evaluate(() => ({ sw: document.documentElement.scrollWidth, cw: document.documentElement.clientWidth }));
    console.log(`LAYOUT viewport ${page.viewportSize()?.width}px scrollWidth ${sw} clientWidth ${cw}`);
    expect(sw).toBeLessThanOrEqual(cw);
  });
});

test.describe('accessibility', () => {
  test('login page has no WCAG 2.2 A/AA violations axe can detect', async ({ page }, info) => {
    test.skip(info.project.name !== 'chromium', 'one engine is enough for an axe scan');
    await page.goto('/login');
    const r = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']).analyze();
    const summary = r.violations.map(v => `${v.id} (${v.impact}, ${v.nodes.length} node${v.nodes.length > 1 ? 's' : ''})`);
    console.log(`AXE ${r.violations.length} rules violated: ${JSON.stringify(summary)} | passes ${r.passes.length} | incomplete ${r.incomplete.length}`);
    expect(r.violations.map(v => v.id)).toEqual([]);
  });
});

test.describe('security', () => {
  test('login response sends the baseline security headers', async ({ request }, info) => {
    test.skip(info.project.name !== 'chromium', 'headers do not depend on the browser');
    const r = await request.get('/login');
    const h = r.headers();
    const want = ['strict-transport-security', 'content-security-policy', 'x-content-type-options', 'x-frame-options', 'referrer-policy', 'permissions-policy'];
    const missing = want.filter(k => !(k in h));
    console.log(`HEADERS status ${r.status()} present: ${JSON.stringify(want.filter(k => k in h))} missing: ${JSON.stringify(missing)}`);
    expect(missing, 'missing security headers').toEqual([]);
  });

  test('session cookie is Secure and HttpOnly after login', async ({ page, context }, info) => {
    test.skip(info.project.name !== 'chromium', 'one engine is enough for cookie flags');
    await page.goto('/login');
    await page.getByLabel('Username').fill('tomsmith');
    await page.getByLabel('Password').fill('SuperSecretPassword!');
    await page.getByRole('button', { name: 'Login' }).click();
    const cookies = await context.cookies();
    console.log(`COOKIES ${JSON.stringify(cookies.map(c => ({ name: c.name, secure: c.secure, httpOnly: c.httpOnly, sameSite: c.sameSite })))}`);
    for (const c of cookies) {
      expect.soft(c.secure, `${c.name} Secure`).toBe(true);
      expect.soft(c.httpOnly, `${c.name} HttpOnly`).toBe(true);
    }
  });
});

test.describe('performance', () => {
  test('home page LCP in a lab run (single sample)', async ({ page }, info) => {
    test.skip(info.project.name !== 'chromium' && info.project.name !== 'mobile-chrome', 'LCP entries are Chromium-only');
    await page.goto('/', { waitUntil: 'load' });
    const lcp = await page.evaluate(() => new Promise<number>(res => {
      new PerformanceObserver(l => { const e = l.getEntries(); res(e[e.length - 1].startTime); }).observe({ type: 'largest-contentful-paint', buffered: true });
    }));
    const nav = await page.evaluate(() => { const n = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; return { ttfb: n.responseStart, load: n.loadEventEnd }; });
    console.log(`PERF ${info.project.name} LCP ${Math.round(lcp)} ms, TTFB ${Math.round(nav.ttfb)} ms, load ${Math.round(nav.load)} ms`);
    expect(lcp).toBeLessThan(2500);
  });
});

The results

The run took 86 seconds with four parallel workers. Two more runs, the last using the code copied back from this page, produced the same 23 passes, 12 failures and 15 skips.

Figure 4 · Our run
50 results, 12 failures to read
Ten checks on five targets. Engine-independent checks ran once, on Chromium.
Valid login reaches the secure area
5 passed
Wrong password rejected with a message
5 passed
Late-loading content waited for
5 passed
Every image renders
5 failed
Every link resolves
5 failed
No horizontal scroll
5 passed
WCAG 2.2 A/AA scan (axe)
1 passed, 4 skipped
Baseline security headers
1 failed, 4 skipped
Session cookie flags
1 failed, 4 skipped
LCP under 2.5 s
2 passed, 3 skipped
23 passed
12 failed
15 skipped by design
Source: our run, Playwright 1.64.1, 28 October 2026, 86 seconds with 4 workers. Two further runs gave the same 23, 12 and 15.

The functional checks passed on all five targets. Valid login, rejected login and late-loading content behaved the same on Chromium, WebKit and Gecko, on desktop and on phones. The layout check passed at 1,280, 412 and 393 pixels wide. Every failure came from content, security or the way a check was written.

Reading the failures: three of twelve were not defects

A failed test is a question, not a verdict. Each of the twelve failures needed a decision before it became a ticket.

Figure 5 · Reading failures
Not every red result is a bug
Each failure needs a verdict before it becomes a ticket.
2 broken images
Defect
asdf.jpg and hjkl.jpg never load. Same result in every browser.
3 links return 401
By design
/basic_auth, /digest_auth and /download_secure ask for a login. The check was wrong, not the app.
4 security headers missing
Finding
No HSTS, CSP, Referrer-Policy or Permissions-Policy on the login page.
Session cookie not Secure
Finding
rack.session is HttpOnly but would also travel over plain HTTP.
4 analytics cookies
Third party
optimizely* cookies have no Secure or HttpOnly flag. Owned by a vendor script, not the app.
Source: our run, 28 October 2026. The 401 pages are documented in the app's README as login-protected.

Two broken images: a real defect

The image check found three images on the page and two that never load: asdf.jpg and hjkl.jpg. The result was identical in every browser, which tells you the problem is the files, not a rendering engine. This is the cheapest kind of defect to catch automatically and the most embarrassing to ship.

Three links returning 401: the check was wrong

The link check found 51 internal links and three that answered 401 Unauthorized: /basic_auth, /digest_auth and /download_secure. The application's README documents those pages as login-protected, with the credentials given. The pages work. The check assumed every link should open without a login, and that assumption was wrong.

This is the most common way an automated suite loses the team's trust. Give link checks an allow-list of protected routes, or accept 401 and 403 on routes you know require authentication. Otherwise the suite fails every night, people learn to ignore it, and a real broken link slips through unseen.

Missing headers and a cookie flag: findings to raise

The login page sent two of six baseline security headers.

Figure 6 · Security headers
Two of six baseline headers sent
Each header is one line of server configuration and closes a whole class of attack.
Strict-Transport-Security
Missing
max-age=31536000; includeSubDomains
Content-Security-Policy
Missing
a policy that blocks inline script, with frame-ancestors
X-Content-Type-Options
Present
nosniff
X-Frame-Options
Present
DENY, or CSP frame-ancestors, which obsoletes it
Referrer-Policy
Missing
strict-origin-when-cross-origin
Permissions-Policy
Missing
switch off the browser features you do not use
Source: response headers of /login in our run, 28 October 2026. Recommended values: OWASP HTTP Security Response Headers Cheat Sheet and WSTG v4.2 test 4.2.7 (HSTS).

Strict-Transport-Security, Content-Security-Policy, Referrer-Policy and Permissions-Policy were all absent. The OWASP HTTP headers cheat sheet notes that the CSP frame-ancestors directive "obsoletes X-Frame-Options" in browsers that support it, so the X-Frame-Options header that was present is the older of the two protections against clickjacking.

The session cookie, rack.session, was HttpOnly, which keeps scripts from reading it. It was not marked Secure, so a browser would also send it over plain HTTP. On a production login, that is a finding worth raising.

Four analytics cookies: not the application's to fix

The same check listed four optimizely cookies with neither Secure nor HttpOnly set. They come from a third-party script, not from the application's own code. They still belong in the report, because they are on the page and the site owner is accountable for them. But the fix is a vendor setting, so they go to a different owner than the session cookie.

What the tools could not decide

Zero accessibility violations is not a pass

The axe scan of the login page reported no violations against WCAG 2.2 levels A and AA.

Figure 7 · Accessibility
Zero violations is not a pass
The scan cannot judge what it cannot see. A person checks the 8 undecided elements.
16
rules passed, including target-size, the WCAG 2.2 rule for touch targets
0
violations found on the login page
1
rule undecided: colour contrast on 8 elements
What axe said about the 8 elements
Element's background color could not be determined because it is overlapped by another element
Source: axe-core 4.14.0 via @axe-core/playwright, tags wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa, 28 October 2026.

It also left one rule undecided. On eight elements, including both form labels and both inputs, something on top of them hid the background colour from the scanner. Contrast on those elements is unknown, not passed. A person needs to check them, and a report that says "0 violations" without the undecided count overstates what was tested.

One speed number is not a result

We measured Largest Contentful Paint on desktop Chrome and on the Pixel 7 profile, once per run, across three runs.

Figure 8 · Speed
One page, three runs, up to 688 ms apart
A single lab sample cannot pass or fail a page on Core Web Vitals.
Desktop Chrome, run 1
2,048 ms
Desktop Chrome, run 2
2,036 ms
Desktop Chrome, run 3
1,564 ms
Pixel 7, run 1
2,316 ms
Pixel 7, run 2
1,628 ms
Pixel 7, run 3
2,020 ms
Bar length is the share of the 2,500 ms target. On an unchanged page, desktop Chrome ranged over 484 ms and the Pixel 7 profile over 688 ms.
Source: our run, Largest Contentful Paint from PerformanceObserver, 28 October 2026. Target: web.dev, LCP within 2.5 seconds at the 75th percentile of real page loads.

Neither profile gave a stable number on an unchanged page. Every sample was under the 2.5-second target, and none of them proves anything on its own. Core Web Vitals are judged on real users at the 75th percentile. Use lab runs to catch regressions between builds, and field data to judge the page.

The web application testing checklist

The table below turns the seven areas into checks with pass criteria, and names the kind of tool that does each job.

AreaWhat to check, and howPass criterion
FunctionalEvery critical flow for every role, error paths included. Automated end-to-end tests plus exploratory sessions.All critical flows pass on the release build
ContentImages render, links resolve, no placeholder text. Automated crawl with an allow-list for protected routes.No failures outside the allow-list
LayoutNo horizontal scroll, nothing clipped. Automated width check at three viewports, then a visual review.Page width never exceeds the viewport
Cross-browserThe same results on each engine. One suite run on Chromium, WebKit and Gecko.Identical results on all three engines
AccessibilityWCAG 2.2 A and AA. Automated scan on every template, then keyboard and screen reader.No violations; undecided items checked by a person
SecurityHeaders, cookie flags, sessions, roles, inputs. Automated header and cookie checks, then manual and scanner testing.Baseline headers present; session cookies Secure and HttpOnly
PerformanceLCP, INP and CLS, and behaviour under load. Lab runs per build, field data, a load test before launch.Field values within target at the 75th percentile

Where automation helps and where people do

Automate a check when it has one right answer and you need it on every build. Keep a check manual when it needs judgement, or when the feature is too new to have settled behaviour.

Figure 9 · Who does what
Automate the repeatable, keep judgement manual
Automate checks with one right answer that run on every build.
Automate
• Login, roles and permissions on every build
• Forms: validation messages and error paths
• Broken images and links, with an allow-list for login-protected routes
• Security headers and cookie flags
• Layout at three viewports
• The axe scan on every page template
Keep manual
• Exploratory testing of new features
• Screen-reader journeys and the elements axe could not decide
• Visual design and copy
• Usability on real phones
• Business rules nobody wrote down
QAble guidance, drawn from the run above.

Web application testing tools in 2027

Pick tools by area, not by brand. One tool rarely covers more than two areas well. The versions below are current as of late October 2026.

End-to-end and cross-browser

  • Playwright (1.64.1) drives Chromium, WebKit and Firefox through one API, which is why we used it for the run above.
  • Selenium (4.51.0, released 9 October 2026) is the long-standing standard, with bindings for Java, Python, C#, Ruby and JavaScript.
  • Cypress (16.2.0) supports Chrome-family browsers, Edge and Firefox, plus experimental WebKit, according to its own documentation.

Accessibility

  • axe-core (4.14.0) is the rules engine inside many accessibility tools, and plugs into Playwright, Selenium and Cypress.

Security

  • ZAP is a free, open-source web application scanner. It left OWASP in September 2023 and describes itself as "an independent Open Source project controlled by the ZAP Core Team and supported by Checkmarx", which is why it is also known as ZAP by Checkmarx.

Performance and load

  • Lighthouse and the browser's own performance APIs measure lab values per page.
  • Grafana k6 is an open-source load testing tool where tests are written in JavaScript and run as code.

If your list of web testing tools still includes QTP or HP UFT, update it: that product is now sold by OpenText. Our broader guide to Web Testing covers simpler sites where a full application suite is more than you need.

Where web application testing differs from website and mobile app testing

A brochure website is mostly content: the main risks are broken links, layout and speed. A web application holds state. Users log in, change data, and expect the next screen to reflect what they did. That adds whole categories of risk: sessions, permissions between roles, data integrity, and concurrent users editing the same record.

A native mobile app adds device concerns that a web application in a phone browser does not have: installation, updates, push notifications, offline behaviour and operating system permissions. A web application tested in a phone browser still needs mobile viewports, touch targets and real-device checks, but not app-store builds.

Scope decides the plan, and it differs from client to client.

From our work
For Exact Sciences, a cancer diagnostics company whose patient-facing web platform serves patients and providers, the scope was desktop web. Our engineers validated the UI across Chrome, Firefox, Safari and Edge, on both Windows and macOS, and audited alt text and meta tags across the content. The case study reports 100% alt and meta audit coverage and zero critical defects missed in release.
For Augmont, a digital gold platform, the same discipline ran across an Admin web portal, a Buyer web app, Android and iOS, through Test, UAT and Production. We logged 515 bugs, 162 of them high severity, and 459 were closed: an 89% resolution rate.
Sources: QAble's Exact Sciences and Augmont case studies, qable.io/casestudy.

The cases that defeat a standard test plan

Login-protected and role-specific pages

As the 401 results showed, an automated crawl sees a protected page as broken. Crawl once per role, with an authenticated session, and treat an unauthenticated 401 on a protected route as the correct answer.

Third-party scripts

Analytics, chat widgets and payment frames run code you did not write, set cookies you did not choose, and can slow pages you did optimise. List every third-party script on the key pages, and test the page with each one blocked to see what it costs.

Experiments and feature flags

A/B tests and feature flags mean two testers on the same build can see different pages. Pin the variant in the test environment, or the suite fails at random and nobody can reproduce it.

Caching and content delivery

A fix can be live on the server and invisible behind a cache. When a fix does not appear, check the response headers for cache status before reopening the defect.

Release gate: seven checks before you ship

Pull the sections above into one gate. If any line fails, the release waits, or ships with a written risk that someone with authority has accepted.

Figure 10 · Release gate
Seven checks before any release
If one fails, the release waits or ships with a written, accepted risk.
☐
Money and data flows
Every critical flow passes for every role on the release build
☐
Engines
The automated suite passes on Chromium, WebKit and Gecko
☐
Phones
The top two phone and browser pairs in your analytics, by hand
☐
Content
No broken image or link outside the allow-list
☐
Accessibility
No axe violations, and the undecided elements checked by a person
☐
Security
Baseline headers present; session cookies Secure and HttpOnly
☐
Speed
Field LCP, INP and CLS within target at the 75th percentile
QAble release checklist, drawn from the sections above.

Conclusion

Web application testing is not a list of testing types. It is a risk map, a browser matrix built from data, seven areas each with its own check, and a habit of reading every failure before filing it. In our runs, three of twelve failures were not defects and one accessibility "pass" was really eight undecided elements. Those details are the difference between a test report and a list of red ticks.

QAble is a QA company in Ahmedabad, India that tests web applications for clients across healthcare, fintech, SaaS and e-commerce. If you want a test plan and a browser matrix built for your application, talk to our web application testing team about a scoped engagement.

Frequently Asked Questions

Can you provide some examples of web application testing?

Three from our own run against a practice application: a login test that enters the right and the wrong password and checks the message each time; an image check that found 2 of 3 images on a page never loaded; and a header check that found 4 of 6 baseline security headers missing from the login response. Each is one short automated test.

How do I test a web application step by step?

Analyse the money-making flows and your real browser mix, plan one line per risk with a pass criterion, prepare an environment with seeded accounts for every role, execute the automated suite on every build plus exploratory sessions on new features, report each defect with steps and severity, retest and regress after every fix, and close with a written record of what was and was not tested.

What are the different types of web application testing?

Seven areas cover it: functional, content integrity, layout and responsiveness, cross-browser, accessibility against WCAG 2.2, security against the OWASP Web Security Testing Guide, and performance against Core Web Vitals. Usability and load testing sit alongside them on larger projects.

Which browsers should I test a web application on?

Test each rendering engine once: a Chromium browser, WebKit and Gecko. In StatCounter's September 2026 data, Chromium browsers (Chrome, Edge, Samsung Internet, Opera) held 76.48% of the worldwide market, Safari 18.27% and Firefox 2.8%. Add at least one Android and one iPhone profile, because mobile is 58.99% of page views, then adjust to your own analytics.

How do you test a web page for accessibility?

Run an automated scanner such as axe-core against WCAG 2.2 A and AA on every page template, then test by hand with a keyboard and a screen reader. Read the scanner's undecided results as well as its violations: on our test page axe found 0 violations but could not decide colour contrast on 8 elements.

What is a web app pen test?

A web application penetration test is a security assessment in which a tester tries to exploit the application the way an attacker would: authentication, sessions, access control, injection and business logic. It goes well beyond the baseline header and cookie checks a functional tester can automate, and usually follows the OWASP Web Security Testing Guide.

What is web service testing, and how is it different?

Web service testing checks the APIs behind an application directly, by sending requests and checking responses, status codes and data, without a browser. Web application testing exercises the same system through the user interface. Most teams need both: API tests are faster and pinpoint the failing service, while browser tests prove the user can complete the task.

Can web application testing be fully automated?

No. Automate the checks with one right answer that need to run on every build: logins, forms, broken links and images, headers, layout widths and accessibility scans. Keep exploratory testing, screen-reader journeys, visual design and usability on real phones manual, because they need judgement.

How long does web application testing take?

It depends on the number of critical flows, roles, browser targets and releases. As a reference point, our ten automated checks ran on five browser targets in 86 seconds with four workers. Writing and maintaining the tests, exploratory sessions and defect triage take far longer than execution, so estimate effort per flow and per role, not per test run.

Why did a link check fail on pages that work?

Because it assumed every link should open without a login. In our run, three links returned 401 Unauthorized, and all three were login-protected by design. Give link checks an allow-list of protected routes, or crawl once per role with an authenticated session, so the suite only fails on real problems.

Is pentesting illegal?

Not when it is authorised. A penetration test is legal when the system owner has given written permission and the scope is agreed in advance. Testing a system without that permission can be a criminal offence, for example under the UK Computer Misuse Act 1990 or the US Computer Fraud and Abuse Act. Never point a scanner at a site you do not own or have written permission to test.

Is a pentester a hacker?

A penetration tester uses the same techniques as an attacker, but with the owner's written permission, an agreed scope and a duty to report every finding so it can be fixed. The permission and the report are the difference. For a web application, the tester's starting point is usually the OWASP Web Security Testing Guide.

Free Assessment

Get a free QA audit for your project

Identify quality gaps before they become production bugs.

Get Free Audit

Ship software with confidence

Talk to a QA advisor and find out how QAble can help your team build quality in at every stage.

No sales pitch
Technical walkthrough
No lock-in commitment

Talk to QA Advisor

Direct access to QAble's QA specialists.

Response within 24 hours