Skip to content

feat: Playwright test to VIN Scanner #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
22 changes: 15 additions & 7 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
branches:
- main
pull_request:
branches: [ main, master ]
branches:
- main
workflow_dispatch:
jobs:
test:
timeout-minutes: 60
env:
HOME: /root
name: 'Playwright Tests'
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.46.1-jammy
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm i
- name: Install dependencies (clean install)
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm test
- uses: actions/upload-artifact@v4
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
path: playwright-report
retention-days: 30
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ CaptureImageModal
.env.local
.env.*.local

# test related files
test-results/
playwright-report/
blob-report/
playwright/.cache/
coverage/
.nyc_output/

# Log files
npm-debug.log*
yarn-debug.log*
Expand Down Expand Up @@ -45,4 +53,4 @@ yarn.lock
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.cache/
32 changes: 23 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,26 +83,40 @@ The following table is a list of supported browsers based on the above requireme

Apart from the browsers, the operating systems may impose some limitations of their own that could restrict the use of the SDK. Browser compatibility ultimately depends on whether the browser on that particular operating system supports the features listed above.

## Testing
## End to End (E2E) testing with Playwright

This repository also includes end-to-end tests for each samples using [Playwright](https://playwright.dev/). The tests are organized per sample, and the setup supports cross-browser testing across Chromium, Firefox, and WebKit.
The end-to-end (E2E) tests for this project are located in the `/e2e` folder.

### Installation
To set up and run the tests, follow the steps below:

``` bash
1. Install the project dependencies:

```bash
npm install
npx playwright install --with-deps # installs playwright browsers
npx playwright install --with-deps # installs Playwright and all required browsers and dependencies
```

2. Run the tests:

```bash
npm test
```

### Running Tests
This command will trigger the Playwright test suite, executing all tests defined in the `/e2e` folder.

### Additional Notes

- **Playwright Configurations:** If you need to customize the Playwright settings (like browser options, timeouts, or environment variables), check the playwright.config.ts file in the root directory.

- **Running Specific Tests:** To run a specific test file or group, use:

```bash
npx playwright test # or `npm test`
npx playwright test <test-file-name>
```

### View Test Reports
- **Generating Reports:** Playwright can generate test reports. You can view detailed results after the tests by running:

```bas
```bash
npx playwright show-report
```

Expand Down
8 changes: 0 additions & 8 deletions VINScanner/tests/index.spec.ts

This file was deleted.

8 changes: 0 additions & 8 deletions VINScanner/tests/minimum-elements.spec.ts

This file was deleted.

33 changes: 33 additions & 0 deletions e2e/assets/sample.y4m

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions e2e/fixtures/VINScanner/MinElements.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Page, Locator } from "@playwright/test";

const URL = "/VINScanner/minimum-elements.html";

export class MinElementsPage {
private page: Page;
private selResolution: Locator;

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

async initialize() {
this.selResolution = await this.page.locator("select.dce-sel-resolution");
}

async navigateTo() {
await this.page.goto(URL);
await this.initialize();
}

async getTitle() {
return await this.page.title();
}

async hasCameraEnhancer() {
const camExists = await this.page.waitForFunction(() => cameraEnhancer !== undefined, { timeout: 5000 });
return camExists;
}

async getResolution() {
await this.hasCameraEnhancer();
const res = await this.page.evaluate(() => {
return cameraEnhancer.getResolution();
});
return { width: res.width, height: res.height };
}

async getAllResolutions() {
await this.page.waitForLoadState("domcontentloaded");
await this.hasCameraEnhancer();
let availableResolutions: { width: number; height: number }[] | null = null;
const maxAttempts = 10;
let attempts = 0;
const delay = 500;

while (attempts < maxAttempts && !availableResolutions) {
availableResolutions = await this.page.evaluate(async () => {
if (typeof cameraEnhancer !== "undefined" && typeof cameraEnhancer.getAvailableResolutions === "function") {
try {
const resolutions = await cameraEnhancer.getAvailableResolutions();
return resolutions && resolutions.length > 0 ? resolutions : null;
} catch (error) {
if (error instanceof OverconstrainedError) {
console.error("OverconstrainedError: Source failed to restart");
return null;
}
throw error;
}
}
return null;
});

if (!availableResolutions) {
await new Promise((resolve) => setTimeout(resolve, delay));
}

attempts++;
}
return availableResolutions;
}

async getCurrentResolution() {
return this.selResolution.getAttribute;
}
async selectResolution() {}
}
111 changes: 111 additions & 0 deletions e2e/fixtures/VINScanner/VINScanner.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Page, Locator } from "@playwright/test";

// TODO: Update the URL when we upload the page to live server.

const URL = "/VINScanner/index.html";

export class VINScannerPage {
private page: Page;
private headerLabel: Locator;
private settingsModal: Locator;
private startButton: Locator;
private scanBarcodeButton: Locator;
private scanTextButton: Locator;
private scanBothButton: Locator;
private dialogCloseButton: Locator;

constructor(page: Page) {
this.page = page;
this.headerLabel = this.page.locator(".scan-mode");
this.settingsModal = this.page.locator(".settings-modal-content");
this.startButton = this.page.locator(".start-btn");

this.scanBarcodeButton = this.page.locator("#scan-barcode-btn");
this.scanTextButton = this.page.locator("#scan-text-btn");
this.scanBothButton = this.page.locator("#scan-both-btn");
this.dialogCloseButton = this.page.locator("i.dls-license-icon-close");
}

async grantCameraPermission() {
await this.page.addScriptTag({
content: `
navigator.mediaDevices.getUserMedia = async () => {
return {
getVideoTracks: () => [{
applyConstraints: () => {},
stop: () => {},
}],
getAudioTracks: () => [],
};
};
`,
});

await this.page.setExtraHTTPHeaders({
"sec-ch-ua": '"Chromium";v="91", " Not;A Brand";v="99"',
});
}

/**
* Close the license related dialog if it shows.
*/
async closeDialogIfPresent() {
this.dialogCloseButton.waitFor({ state: "visible", timeout: 5000 });
await this.dialogCloseButton.click();
}

async navigateTo() {
await this.grantCameraPermission();
await this.page.goto(URL);
await this.closeDialogIfPresent();
}

async getTitle() {
return await this.page.title();
}

async getSelectedButton() {
return await this.page.locator("button.scan-option-btn.selected");
}

async getHeaderLabel(expectedText?: string) {
await this.page.waitForTimeout(3000);

// If expectedText is provided, wait for it to appear
if (expectedText) {
await this.headerLabel.waitFor({ state: "visible", timeout: 5000 });
await this.page.waitForFunction((expectedText) => {
this.headerLabel.textContent().then.toString().match(expectedText);
}, expectedText);
}

return await this.headerLabel.textContent();
}

async waitForSettingsModal() {
await this.settingsModal.waitFor({ state: "visible" });
}

async clickStartButton() {
await this.startButton.click();

// Ensuring the page is loaded after clicked on the Start button
await this.page.waitForLoadState("networkidle", { timeout: 30000 });
await this.page.waitForLoadState("domcontentloaded");
}

async clickScanBarcodeButton() {
await this.scanBarcodeButton.click();
await this.page.waitForTimeout(2000);
}

async clickScanTextButton() {
await this.scanTextButton.click();
await this.page.waitForTimeout(2000);
}

async clickScanBothButton() {
await this.scanBothButton.click();
await this.page.waitForTimeout(2000);
}
}
4 changes: 4 additions & 0 deletions e2e/fixtures/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare var cameraEnhancer: {
getResolution: () => { width: number; height: number };
getAvailableResolutions: () => Promise<{ width: number; height: number }[]>;
};
19 changes: 19 additions & 0 deletions e2e/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { test as baseTest } from "@playwright/test";
import { VINScannerPage } from "./VINScanner/VINScanner.fixture";
import { MinElementsPage } from "./VINScanner/MinElements.fixture";

export const test = baseTest.extend<{
vinScannerPage: VINScannerPage;
minElementsPage: MinElementsPage;
}>({
vinScannerPage: async ({ page }, use) => {
const vinScannerPage = new VINScannerPage(page);
await use(vinScannerPage);
},
minElementsPage: async ({ page }, use) => {
const minElementsPage = new MinElementsPage(page);
await use(minElementsPage);
},
});

export { expect } from "@playwright/test";
Loading
Loading