Skip to content

Commit

Permalink
E2E tests with associated ci.yml changes
Browse files Browse the repository at this point in the history
  • Loading branch information
andrejrblue committed Nov 18, 2024
1 parent 74fe7d5 commit ac6a483
Show file tree
Hide file tree
Showing 9 changed files with 592 additions and 1 deletion.
27 changes: 26 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
CMSSHOPIFYCONFIG__SHOPIFYURL: ${{ secrets.SHOPIFY_URL }}
CMSSHOPIFYCONFIG__STOREFRONTAPIKEY: ${{ secrets.STOREFRONT_API_KEY }}
CMSSHOPIFYCONFIG__STOREFRONTAPIVERSION: ${{ secrets.STOREFRONT_API_VERSION }}
CMSSHOPIFYCONFIG__STOREPASSWORD: ${{ secrets.SHOPIFY_STORE_PASSWORD }}

steps:
- uses: actions/checkout@v4
Expand All @@ -57,6 +58,18 @@ jobs:
with:
global-json-file: global.json

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*

- name: Install NPM
run: |
cd test/Playwright
npm ci
npx playwright install --with-deps
cd ../..
- name: Install dependencies
run: |
dotnet restore `
Expand Down Expand Up @@ -170,9 +183,21 @@ jobs:
exit 1
}
# TODO: Run the E2E tests
# Sleep for finishing initialization
Start-Sleep -Seconds 10
# Run the E2E tests
cd test/Playwright
npx playwright test
cd ../..
# Stop the background ASP.NET Core application
Receive-Job -Name ${{ env.PROJECT_NAME }}
Stop-Job -Name ${{ env.PROJECT_NAME }}
Remove-Job -Name ${{ env.PROJECT_NAME }}
- uses: actions/upload-artifact@v4
with:
name: playwright-report
path: ./test/Playwright/playwright-report/
retention-days: 30
5 changes: 5 additions & 0 deletions test/Playwright/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
62 changes: 62 additions & 0 deletions test/Playwright/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Playwright E2E tests

## Description

E2E tests are implemented using Playwright - an open-source, NodeJS-based framework for web testing and automation.

To prevent false-positives, tests are configured to run maximum of 2 times based on the result of the 1st test. Following combinations apply.

- test PASSES the 1st run -> Test **PASSED**
- test FAILS the 1st run, retries and PASSES the 2nd run -> Test **PASSED** with **FLAKY** signature
- test FAILS the 1st, retries and FAILS the 2nd run -> Test **FAILED**

## Install dependencies

`npm i`

`npx playwright install --with-deps`

## Run tests

Following env variables are expected to be set

- ASPNETCORE_URLS = Base url of the app
- CMSSHOPIFYCONFIG\_\_ADMINAPIKEY = Shopify API access token
- CMSSHOPIFYCONFIG\_\_SHOPIFYURL = Shopify store URL
- CMSSHOPIFYCONFIG\_\_STOREPASSWORD = Shopify store password

Run the tests with command

`npx playwright test`

## Test results

Test results are generated whether the tests pass or fail.

#### HTML

HTML test result can be opened using

`npx playwright show-report`

#### JUnit

Test results are generated also in JUnit format, for possible integration with other reporting systems. XML file can be found in `test-results/e2e-junit-results.xml`

#### Artifacts

In case of a test failure, following artifacts are created

- Screenshot of the last application state (point of failure) - generates on every fail
- Video of full test run - generates on every fail
- Trace - generates on first retry

Trace contains enhanced information to ease debugging failures. It can be opened with Trace Viewer (GUI tool) locally or using hosted variant by Playwright where it is possible to upload trace files using drag and drop.

To open local Trace Viewer use

`npx playwright show-trace path/to/trace.zip`

Hosted variant by Playwright can be found at

[trace.playwright.dev](https://trace.playwright.dev/)
97 changes: 97 additions & 0 deletions test/Playwright/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions test/Playwright/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "playwright",
"version": "1.0.0",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@playwright/test": "^1.48.2",
"@types/node": "^22.9.0"
}
}
36 changes: 36 additions & 0 deletions test/Playwright/pageObjects/BasePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { type Locator, type Page } from "@playwright/test";

export class BasePage {
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async goto(url: string) {
await this.page.goto(url);
await this.fullyLoadPage();
}
async fullyLoadPage() {
await this.page.evaluate(() =>
document.querySelectorAll("img[loading=lazy]").forEach((img) => img.setAttribute("loading", "eager"))
);

await this.page.evaluate(async () => {
await new Promise((resolve) => {
let totalHeight = 0;
const distance = 200;
const timer = setInterval(() => {
var scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= scrollHeight - window.innerHeight) {
clearInterval(timer);
resolve(void 0);
}
}, 70);
});
window.scrollTo(0, 0);
});
}
}
61 changes: 61 additions & 0 deletions test/Playwright/pageObjects/CheckoutPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { type Locator, type Page } from "@playwright/test";
import { BasePage } from "./BasePage";

export class CheckoutPage extends BasePage {
readonly $firstName: Locator;
readonly $lastName: Locator;
readonly $email: Locator;
readonly $address: Locator;
readonly $city: Locator;
readonly $psc: Locator;
readonly $country: Locator;
readonly $shippingMethods: Locator;
readonly $cardNumber: Locator;
readonly $cardExpirationDate: Locator;
readonly $cardSecurityCode: Locator;
readonly $payBtn: Locator;

constructor(page: Page) {
super(page);

this.$firstName = page.locator('#TextField0[name="firstName"]');
this.$lastName = page.locator('#TextField1[name="lastName"]');
this.$email = page.locator('input[name="email"]');
this.$address = page.locator('#TextField2[name="address1"]');
this.$city = page.locator('#TextField5[name="city"]');
this.$psc = page.locator('#TextField4[name="postalCode"]');
this.$country = page.locator('select[name="countryCode"]');
this.$shippingMethods = page.locator("#shipping_methods");
this.$cardNumber = page
.frameLocator('iframe[title="Field container for: Card number"]')
.getByPlaceholder("Card number");
this.$cardExpirationDate = page
.frameLocator('iframe[title="Field container for: Expiration date (MM / YY)"]')
.getByPlaceholder("Expiration date (MM / YY)");
this.$cardSecurityCode = page
.frameLocator('iframe[title="Field container for: Security code"]')
.getByPlaceholder("Security code");
this.$payBtn = page.locator("#checkout-pay-button");
}

async fillCustomerDetails(details) {
await this.$firstName.fill(details.firstName);
await this.$lastName.fill(details.lastName);
await this.$email.fill(details.email);
await this.$address.fill(details.address);
await this.$psc.fill(details.psc);
await this.$city.fill(details.city);
await this.$country.selectOption({ label: details.country });
}
async selectShipping(shipping) {
await this.$shippingMethods.locator("label", { hasText: shipping }).click();
}
async fillPayment(payment) {
await this.$cardNumber.fill(payment.cardNumber);
await this.$cardExpirationDate.fill(payment.cardExpirationDate);
await this.$cardSecurityCode.fill(payment.cardSecurityCode);
}
async confirmOrder() {
await this.$payBtn.click();
}
}
Loading

0 comments on commit ac6a483

Please sign in to comment.