Kihagyás

Playwright

Playwright is a browser-automation framework developed by Microsoft. It controls Chromium, Firefox, and WebKit through a common API and can be used for end-to-end (E2E) testing and other browser-automation tasks.

Testing levels and tools

  • Unit testing: verifies individual functions, methods, or classes in isolation.

  • Integration testing: verifies interactions between components, such as an application service and a database.

  • End-to-end (E2E) testing: verifies complete user workflows through the integrated system, including the browser, frontend, backend, and related services.

In the examples used in this material:

  • Flask is the small backend application,
  • Playwright is the browser-based E2E tester,
  • pytest executes conventional automated tests and the random exploratory example;
  • Gherkin describes scenarios in the Given–When–Then format, and Behave executes them through Python step definitions.

Random UI exploration and BDD

The random exploratory test traverses the UI by selecting actions such as:

  • clicks,
  • typing,
  • scrolling,
  • assigning a score to certain observations, such as an HTTP 500 response.

This is random exploratory testing rather than reinforcement learning: the score does not influence later action selection, so the program does not learn a policy.

A Cucumber/behave-style test:

  • describes specific business processes:
    • “If the user fills the form and submits it, they should see the submitted data.”
    • “If the user goes to the Error page and presses the button, the server returns HTTP 500.”
  • Human-readable in Gherkin format: Given – When – Then.

The two approaches complement one another:

  • random exploration can reveal unexpected failures and unusual states;
  • BDD scenarios verify specified business workflows.

Create the demo application

1
python3 -m venv venv

Activate this environment:

1
source venv/bin/activate

Install Playwright:

1
pip install playwright

Install the browsers:

1
playwright install

This installs the Chromium, WebKit, and Firefox browser binaries required by Playwright.

Install pytest:

1
pip install pytest

Install Flask:

1
pip install flask

Flask works well as a lightweight application server in development environments.

Create the following demo_app.py file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from flask import Flask, render_template_string, request

app = Flask(__name__)

PAGE = """
<!doctype html>
<title>UI Testing Demo</title>
<h1>UI Testing Demo</h1>

<nav>
    <a href="/">Home</a> |
    <a href="/form">Form</a> |
    <a href="/error">Error</a>
</nav>

{% if page == "home" %}
<p>Welcome on the home page.</p>
{% elif page == "form" %}
<form method="post">
    <label>Username: <input name="username"></label><br>
    <label>Age: <input name="age"></label><br>
    <button type="submit">Submit</button>
</form>
{% if submitted %}
    <p>Submitted: {{ username }} ({{ age }})</p>
{% endif %}
{% elif page == "error" %}
    {% if trigger_error %}
        {% set x = 1 / 0 %}
    {% else %}
        <p>Click the button to trigger server error.</p>
        <form method="post">
            <button type="submit">Trigger error</button>
        </form>
    {% endif %}
{% endif %}
"""

@app.route("/", methods=["GET"])
def index():
    return render_template_string(PAGE, page="home")

@app.route("/form", methods=["GET", "POST"])
def form():
    submitted = False
    username = ""
    age = ""
    if request.method == "POST":
        submitted = True
        username = request.form.get("username", "")
        age = request.form.get("age", "")
    return render_template_string(
        PAGE,
        page="form",
        submitted=submitted,
        username=username,
        age=age,
    )

@app.route("/error", methods=["GET", "POST"])
def error_page():
    trigger_error = (request.method == "POST")
    # A POST request intentionally triggers an HTTP 500 response.
    return render_template_string(PAGE, page="error", trigger_error=trigger_error)


if __name__ == "__main__":
    app.run(port=5000, debug=False, use_reloader=False)

This application provides a small menu, a form, and a page that intentionally produces an HTTP 500 response. It can be used for deterministic BDD scenarios as well as random exploratory testing.

Create Behave tests

Setting up the environment:

1
2
source venv/bin/activate
pip install behave

Directory structure:

1
2
3
4
5
6
7
8
.
├── demo_app.py
├── test_random_explorer.py  # optional pytest + Playwright exploratory test
└── features/
    ├── ui_demo.feature      # Gherkin scenarios
    ├── environment.py       # Behave hooks
    └── steps/
        └── ui_steps.py      # Python step definitions

Gherkin feature

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Feature: Demo application behaviour
  The application provides a home page, a form, and an error page.

Background:
  Given the demo app is running
  And I open the application in the browser

Scenario: Successful form submission
  When I navigate to the form page
  And I fill the form with username "Alice" and age "42"
  And I submit the form
  Then I should see "Submitted: Alice (42)"

Scenario: Triggering internal server error
  When I navigate to the error page
  And I trigger the server error
  Then the server should return HTTP 500

Important: the scenarios assume that demo_app.py is already running in a separate terminal.

Behave environment with Playwright

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# features/environment.py
from playwright.sync_api import sync_playwright

def before_all(context):
    context.playwright = sync_playwright().start()
    context.browser = context.playwright.chromium.launch(headless=True)

def before_scenario(context, scenario):
    context.browser_context = context.browser.new_context()
    context.page = context.browser_context.new_page()

def after_scenario(context, scenario):
    context.browser_context.close()

def after_all(context):
    context.browser.close()
    context.playwright.stop()

Each scenario receives an isolated browser context and page. This prevents cookies, local storage, and page state from leaking between scenarios.

Step definitions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# features/steps/ui_steps.py
from behave import given, when, then
from playwright.sync_api import expect

BASE_URL = "http://localhost:5000"


@given("the demo app is running")
def step_app_running(context):
    response = context.page.request.get(BASE_URL)
    assert response.ok, f"Application health check failed: HTTP {response.status}"


@given("I open the application in the browser")
def step_open_app(context):
    context.page.goto(BASE_URL)


@when("I navigate to the form page")
def step_goto_form(context):
    context.page.goto(f"{BASE_URL}/form")


@when('I fill the form with username "{username}" and age "{age}"')
def step_fill_form(context, username, age):
    # in demo_app.py:
    # <input name="username"> and <input name="age">
    context.page.fill('input[name="username"]', username)
    context.page.fill('input[name="age"]', age)


@when("I submit the form")
def step_submit_form(context):
    context.page.click("button[type=submit]")


@then('I should see "Submitted: {username} ({age})"')
def step_see_submitted(context, username, age):
    expected = f"Submitted: {username} ({age})"
    expect(context.page.get_by_text(expected, exact=True)).to_be_visible()


@when("I navigate to the error page")
def step_goto_error(context):
    context.page.goto(f"{BASE_URL}/error")


@when("I trigger the server error")
def step_trigger_error(context):
    with context.page.expect_response(
        lambda response: (
            response.url == f"{BASE_URL}/error"
            and response.request.method == "POST"
        )
    ) as response_info:
        context.page.get_by_role("button", name="Trigger error").click()

    context.error_response = response_info.value


@then("the server should return HTTP 500")
def step_see_internal_error(context):
    assert context.error_response.status == 500

Terminal 1 — run the application:

1
2
source venv/bin/activate
python demo_app.py

Terminal 2 — run the Behave scenarios:

1
2
source venv/bin/activate
behave

Behave now executes the scenarios defined in features/ui_demo.feature through Playwright.


Utolsó frissítés: 2026-09-04 08:34:02