Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 133 additions & 9 deletions .github/workflows/robot-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
# into `main` (GitHub only reads `schedule` triggers from the workflow
# file version on the repo's default branch, not from feature branches).
# schedule:
# - cron: '0 3 * * *' # 03:00 UTC = 00:00 (meia-noite) em Brasília
# - cron: '0 3 * * *' # 03:00 UTC = 00:00 (midnight) in Brasília
workflow_dispatch: # allows manual trigger from the Actions tab
inputs:
tag:
Expand All @@ -26,6 +26,11 @@ concurrency:
group: robot-tests-${{ github.ref }}
cancel-in-progress: true

# Needed by peaceiris/actions-gh-pages to push the Allure report
# to the gh-pages branch using the default GITHUB_TOKEN.
permissions:
contents: write

jobs:
robot-tests:
name: Run Robot Framework Suite
Expand All @@ -34,7 +39,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Determine which tag to run
id: tag
Expand All @@ -52,13 +57,13 @@ jobs:
fi

- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'

- name: Set up Node.js (required by the Browser library driver)
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '22'

Expand All @@ -68,7 +73,7 @@ jobs:
pip install -r requirements.txt

- name: Cache Playwright browsers
uses: actions/cache@v4
uses: actions/cache@v5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
Expand All @@ -82,6 +87,7 @@ jobs:
- name: Run Robot Framework tests
run: |
robot \
--listener allure_robotframework:allure-results \
--variable HEADLESS:True \
--include ${{ steps.tag.outputs.value }} \
--outputdir results \
Expand All @@ -96,6 +102,9 @@ jobs:
# critical-path subset run on every PR; "regression" covers the
# whole suite (every test carries it) and runs on merges to main
# and the nightly schedule.
# --listener allure_robotframework writes the raw Allure data to
# allure-results/ (at the root of the workspace, outside results/),
# which is converted into the HTML report in the steps below.

- name: Publish summary on GitHub Actions
if: always()
Expand All @@ -108,20 +117,135 @@ jobs:
echo "for smoke/regression/critical/high/medium and ui) is"
echo "available in the **robot-framework-results** artifact."
fi
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo ""
echo "Allure report (with history): https://tfernandes-qa.github.io/robotframework/"
fi
} >> "$GITHUB_STEP_SUMMARY"

- name: Upload artifacts (log.html, report.html, output.xml)
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: robot-framework-results
path: results/
retention-days: 15

- name: Upload JUnit/xUnit report
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: robot-junit-report
path: xunit.xml
retention-days: 15
path: results/xunit.xml
retention-days: 15

# ---------- Allure ----------
#
# NOTE: previously used simple-elf/allure-report-action, but that
# action's Docker image (openjdk:8-jre-alpine) was removed from
# Docker Hub upstream and the action is unmaintained, so the report
# generation is now done directly with the Allure CLI (installed via
# npm) instead of that action.

- name: Install Allure CLI
if: always()
run: npm install -g allure-commandline

# Checks whether gh-pages exists yet (it won't on the very first run,
# before any deploy to Pages has happened), to skip the checkout below
# cleanly instead of letting it fail with retries.
- name: Check if gh-pages branch exists
if: always()
id: gh-pages-check
run: |
if git ls-remote --exit-code --heads origin gh-pages > /dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

# Retrieves the history of previous runs from the gh-pages branch.
- name: Get Allure history
if: always() && steps.gh-pages-check.outputs.exists == 'true'
uses: actions/checkout@v5
with:
ref: gh-pages
path: gh-pages

# Copies the previous run's history/ folder into allure-results so
# the Allure CLI picks it up and renders the trend graphs.
- name: Restore Allure history
if: always()
run: |
if [ -d "gh-pages/history" ]; then
cp -r gh-pages/history allure-results/history
else
echo "No previous history found (gh-pages branch or history/ folder doesn't exist yet) — skipping."
fi

- name: Generate Allure report with history
if: always()
run: allure generate allure-results --clean -o allure-report

# Publishes to GitHub Pages only on pushes to main (and manual runs on
# main), so that PRs with the "smoke" subset don't pollute the official
# trend/flaky-test history, which is fed only by the full suite.
- name: Deploy Allure report to GitHub Pages
if: always() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: allure-report

# On PRs, the Allure report is made available as a run artifact.
- name: Upload Allure report artifact (PRs)
if: always() && github.event_name == 'pull_request'
uses: actions/upload-artifact@v6
with:
name: allure-report
path: allure-report/
retention-days: 15

# ---------- Email with Allure report ----------

# Single-file version of the report: a self-contained index.html
# that's ideal as an email attachment.
- name: Generate single-file Allure report (email attachment)
if: always()
run: allure generate allure-results --single-file --clean -o allure-single

- name: Send Allure report by email
if: always()
uses: dawidd6/action-send-mail@v14
with:
server_address: ${{ secrets.MAIL_SERVER }}
server_port: 465
secure: true
username: ${{ secrets.MAIL_USERNAME }}
password: ${{ secrets.MAIL_PASSWORD }}
from: Robot CI <${{ secrets.MAIL_USERNAME }}>
to: ${{ secrets.MAIL_TO }}
subject: >-
[${{ job.status == 'success' && 'PASS' || 'FAIL' }}]
Robot ${{ steps.tag.outputs.value }} —
${{ github.repository }} run #${{ github.run_number }}
html_body: |
<h3>Robot Framework — run result</h3>
<p>
<b>Status:</b> ${{ job.status }}<br>
<b>Suite:</b> ${{ steps.tag.outputs.value }}<br>
<b>Event:</b> ${{ github.event_name }}<br>
<b>Branch/ref:</b> ${{ github.ref_name }}<br>
<b>Commit:</b> ${{ github.sha }}
</p>
<p>
Full Allure report attached (index.html — opens directly in the browser).<br>
Run details:
<a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">
run #${{ github.run_number }}
</a><br>
Published history (main):
<a href="https://tfernandes-qa.github.io/robotframework/">tfernandes-qa.github.io/robotframework</a>
</p>
attachments: allure-single/index.html
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ report.html
output.xml
playwright-log.txt
browser/traces/

# Allure
allure-results/
allure-report/
allure-history/
gh-pages/
101 changes: 89 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ robotframework/
├── requirements.txt
├── resources/
│ ├── api/
│ │ ├── products_api.resource
│ │ └── users_api.resource
│ ├── pages/
│ │ ├── global_page.resource
Expand Down Expand Up @@ -96,6 +97,11 @@ random test data via Faker (`pt_BR` locale):
the API, for tests that need to reach the admin area.
- `Cleanup Login Test` — closes the browser and removes, via the API, the
user created in the setup.
- `Invalidate User Session Token` — clears the browser's local storage,
simulating an expired or otherwise invalid session.
- `Alert Message Should Be` — asserts the generic dismissible alert
message shared by several forms across the app (login, new user, new
product, ...).
- `Generate Random User` — returns a random name, email, and password
(via `Generate Random Password`), using Faker.
- `Generate Random Password` — generates a random alphanumeric password
Expand Down Expand Up @@ -177,20 +183,23 @@ Page object for the admin's "create product" screen. Contains:

Page object for the store/home screen's shopping list feature. Contains:

- **Locators**: `${HOME_BUTTON}`, `${ADD_TO_LIST_BUTTON}`,
- **Locators**: `${HOME_BUTTON}`, `${INCREASE_BUTTON}`, `${DECREASE_BUTTON}`,
`${CLEAR_LIST_BUTTON}`, `${CART_LIST_EMPTY}`.
- **Keywords**:
- `Back To Home` — clicks the "Página Inicial" button to return to the
home page.
- `Clear List` — clicks the button to clear the shopping list.
- `Add Item To List` — finds a product card by its name and clicks the
button to add it to the shopping list.
- `Add Item To List` — finds a product card by its name (built as a
dynamic XPath, no fixed locator) and clicks the button to add it to
the shopping list.
- `Item Should Be In List` — verifies that the given product is visible
in the shopping cart list.
- `Increase Quantity of Item in List` — clicks the button to increase the
quantity of an item already in the shopping list.
- `Item Should Be Increased` — verifies that the quantity of the given
product has been increased in the shopping list.
- `Increase Quantity of Item in List` / `Decrease Quantity of Item in
List` — click the buttons to increase/decrease the quantity of an item
already in the shopping list.
- `Item Should Be Increased` / `Item Should Be Decreased` — verify that
the quantity of the given product went up/down accordingly in the
shopping list.
- `List Should Be Empty` — verifies that the shopping list is empty.

### `resources/api/users_api.resource`
Expand All @@ -206,6 +215,35 @@ through the UI:
`administrador=true`, so it can log in and reach the admin area.
- `Delete User Via Api` — deletes the user (by id) created for the test,
keeping the test environment clean.
- `Get Users By Email Via Api` — queries all users registered with a given
email, returning how many were found and the matching records.
- `Delete All Users With Email Via Api` — deletes every user registered
with a given email. Used to clean up after tests that intentionally
create duplicated users (e.g. the double-click and duplicated-email
tests), where the id isn't known upfront.

### `resources/api/products_api.resource`

Support keywords that call the ServeRest REST API to prepare and clean up
test data (products) used by the UI tests. Unlike the user routes, the
product routes require an admin authorization token:

- `Get Admin Auth Token` — logs in with the given admin credentials and
returns the authorization token required by the product creation/deletion
routes.
- `Create Product Via Api` — creates a product with the given fields, using
the given admin token, and returns the id of the created product.
- `Delete Product Via Api` — deletes the product (by id) created for the
test, using the given admin token.
- `Get Products By Name Via Api` — queries all products registered with a
given name, returning how many were found and the matching records.
- `Delete All Products With Name Via Api` — deletes every product
registered with a given name, using the given admin token. Used to clean
up after tests that intentionally create duplicated products.
- `Cleanup Product By Name Via Api` — obtains a fresh admin token with the
given credentials and removes every product registered with a given
name. Used to clean up after tests that create a product through the UI,
where no product id is known.

### `tests/login/login.robot`

Expand All @@ -229,13 +267,32 @@ to the page object keywords described above.
### `tests/store/store.robot`

Test suite covering the shopping list on the ServeRest store/home page. It
reuses `Prepare Login Test`/`Cleanup Login Test` (via `Open Browser To Home
Page`) to log in as a fresh API-created user before each test, then drives
the store page object to run scenarios such as:
reuses `Prepare Login Test` (via `Open Browser To Home Page`) to log in as a
fresh API-created shopper user before each test, then drives the store page
object to run scenarios such as:

- Adding two items to the shopping list.
- Increasing the quantity of an item already in the list.
- Clearing the shopping list.
- Increasing/decreasing the quantity of an item already in the list.

Each `Given` step creates its own product(s) via the API (`Create Admin
User Via Api` + `Get Admin Auth Token` + `Create Product Via Api`, with a
Faker word name suffixed with a random string for uniqueness) instead of
relying on fixed catalog items, since the ServeRest catalog is a public,
shared demo environment where fixed product names can collide with
clutter created by other students/QA courses (this was observed in
practice: a duplicated "Logitech MX Vertical" card broke a locator in
strict mode). Because the store/home page has already loaded before the
`Given` step runs, it calls Browser library's `Reload` afterwards so the
newly created product's card appears before the `When` steps interact
with it.

Each test case defines its own `[Teardown]`, chained with `AND` onto
`Cleanup Login Test`, to delete the product(s) and the temporary admin
user created for setup — a local `[Teardown]` replaces the suite's `Test
Teardown` rather than running in addition to it, so `Cleanup Login Test`
must be included explicitly in every custom teardown, or the browser
never closes and the shopper user is never deleted.

The `*** Keywords ***` section of this file defines the Given/When/Then
style keywords used by the test cases (e.g. `the user adds the first item to
Expand All @@ -256,6 +313,26 @@ scenarios such as:
- Creating a new product with randomly generated data (via `Generate
Random Product`) and confirming it appears in the products table.
- Listing all products and confirming the products table is displayed.
- An admin user being able to access the regular user's home page (store)
directly, without being blocked.
- The admin not being able to create a user or a product with a name/email
that already exists (negative cases), asserting the corresponding error
message.
- Double-clicking the submit button on the new user form not creating two
duplicated users.

The five tests above that create extra data (new user, new product, the
two "cannot create duplicated ..." cases, and the double-click case) each
define their own `[Teardown]`, chained with `AND`
onto `Cleanup Login Test`, to delete that data via the API (e.g. `Delete
User Via Api`, `Delete All Users With Email Via Api`, `Cleanup Product By
Name Via Api`). A local `[Teardown]` replaces the suite's `Test Teardown`
instead of running in addition to it, so `Cleanup Login Test` has to be
included explicitly every time — and because `Run Keywords` only chains
multiple keywords when they're separated with `AND` (a single keyword
passed to it gets its arguments misread as more keyword names to run), a
teardown that runs just one cleanup keyword must call it directly instead
of wrapping it in `Run Keywords`.

The `*** Keywords ***` section of this file defines the Given/When/Then
style keywords used by the test cases (e.g. `the admin fills in the user
Expand All @@ -271,7 +348,7 @@ without touching test code:

| Dimension | Tags | Meaning |
|-------------------|------------------------------------|---------|
| **Execution set** | `smoke`, `regression` | `regression` is on every test (the full suite). `smoke` marks the small, fast subset of critical happy paths — currently 5 of the 14 tests — meant to run on every PR for quick feedback. |
| **Execution set** | `smoke`, `regression` | `regression` is on every test (the full suite). `smoke` marks the small, fast subset of critical happy paths — currently 5 of the 21 tests — meant to run on every PR for quick feedback. |
| **Criticality** | `critical`, `high`, `medium` | `critical` = core journeys the app is unusable without (login, admin create user/product, add to cart). `high` = important supporting flows (listing, quantity, clearing). `medium` = negative/validation edge cases. |
| **Layer** | `ui` | All current tests drive the browser end-to-end (API is only used for setup/teardown). Kept as an explicit tag so future API-only suites can be filtered out (`--exclude ui`) or in (`--include ui`) separately. |

Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
robotframework
robotframework-browser
robotframework-requests
Faker==26.0.0
Faker==26.0.0
allure-robotframework
Loading