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.
-
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
Activate this environment:
Install Playwright:
Install the browsers:
This installs the Chromium / WebKit / Firefox drivers into the venv.
Install pytest:
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:
| source venv/bin/activate
pip install behave
|
Directory structure:
| .
├── 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:
| source venv/bin/activate
python demo_app.py
|
behave (CCucumber-style tests)
| 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