Kihagyás

Playwright

Playwright is a modern, fast, and reliable browser automation framework originally developed by Microsoft. It allows you to control real browsers (Chromium, Firefox, WebKit) from code, making it easy to write end-to-end (E2E) tests for web applications.

Testing levels and tools

  • Unit test: you test the behavior of individual functions or classes (e.g., pytest unit tests).

  • Integration / end-to-end (E2E) test: a larger slice of the system works together (frontend + backend, form submission, navigation, errors, etc.).

AIn the example shown in this material:

  • Flask is the small backend application,
  • Playwright is the browser-based E2E tester,
  • pytest: “traditional” automated test (now with RL-like random UI exploration),
  • Cucumber / Gherkin (behave): scenario-based UI tests close to natural language (Given–When–Then).

RL-like UI exploration vs. Cucumber / BDD

Your RL-like test (test_rl_explorer.py) traverses the UI with random actions:

  • clicks,
  • typing,
  • scrolling,
  • and rewarding/penalizing (reward) certain events (e.g., 500 errors).

(See: AI-based testing chapter)

This is more like exploration — “what happens if I randomly press everything”.

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, they get a 500 error.”
  • Human-readable in Gherkin format: Given – When – Then.

The two don’t exclude each other; they complement one another:

  • RL-like exploration: unexpected errors, weird states,
  • Cucumber/BDD: correctness of “business” workflows.

Create demo_app

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 / Firefox drivers into the venv. 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>RL Demo App</title>
<h1>RL Demo – Simple UI</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")
    # Ha POST érkezik, szándékosan kivételt dobunk -> 500-as hiba
    return render_template_string(PAGE, page="error", trigger_error=trigger_error)


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

This application provides a small menu, a form, and an intentionally 500-error page — ideal for RL-style exploration.

Behave test

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_rl_explorer.py      # the existing pytest + Playwright RL test
└── features/
    ├── rl_demo.feature      # Gherkin description
    ├── environment.py       # behave hooks (start/stop)
    └── steps/
        └── rl_steps.py      # step definitions in Python

Gherkin feature

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Feature: RL Demo Flask app basic behaviour
  A small demo app with home page, form and an error page.
  We want to describe some basic end-to-end scenarios in Gherkin.

Background:
  Given the RL 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 I should see an internal server error page

Important: just like with the pytest test, we assume that demo_app.py is already running in the background (python demo_app.py).

Behave environment with Playwright

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

def before_all(context):
    # Start Playwright
    context.playwright = sync_playwright().start()
    # headless can be True if you don't need a graphical window
    context.browser = context.playwright.chromium.launch(headless=False)
    context.page = context.browser.new_page()

def after_all(context):
    # Shut down browser and Playwright
    context.browser.close()
    context.playwright.stop()

The context.page becomes accessible in all steps.

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
64
65
66
# features/steps/rl_steps.py
from behave import given, when, then
from playwright.sync_api import Error as PlaywrightError

BASE_URL = "http://localhost:5000"


@given("the RL Demo app is running")
def step_app_running(context):
    # We simply assume it's running (python demo_app.py)
    # If you want, you can add a simple health check here (e.g., requests.get).
    pass


@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})"
    content = context.page.content()
    assert expected in content, f"Expected to see {expected!r}, got:\n{content}"


@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):
    # The error page has a form + button that triggers the 1/0 error with POST
    try:
        context.page.click("button[type=submit]")
    except PlaywrightError as e:
        # If the page closes or Playwright throws, that is also an “error” phenomenon,
        # but usually with a 500 response we just see the error page.
        print("Playwright error while triggering server error:", repr(e))


@then("I should see an internal server error page")
def step_see_internal_error(context):
    content = context.page.content()
    # Flask’s default 500 template contains the text "Internal Server Error"
    assert ("Internal Server Error" in content
            or "500" in content), "Expected an internal server error indication"

Running:

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

behave (CCucumber-style tests)

1
2
source venv/bin/activate
behave

At this point the scenarios defined in features/rl_demo.feature run through Playwright.


Utolsó frissítés: 2025-11-30 16:47:47