Skip to content

Commit c08e588

Browse files
Felix-Dynamsoftfelixindrawan
authored andcommitted
feat(VINScanner): allow vertical scan on barcode mode (#11)
1 parent caaa0e5 commit c08e588

17 files changed

+626
-8
lines changed

.github/workflows/playwright.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ jobs:
2424
with:
2525
name: playwright-report
2626
path: playwright-report/
27-
retention-days: 30
27+
retention-days: 30

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ CaptureImageModal
88
.env.local
99
.env.*.local
1010

11+
# test related files
12+
test-results/
13+
playwright-report/
14+
blob-report/
15+
playwright/.cache/
16+
coverage/
17+
.nyc_output/
18+
19+
1120
# Log files
1221
npm-debug.log*
1322
yarn-debug.log*

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@
66
| ------------ | ------------------------------------------------------------------------------------ |
77
| `VIN Scanner` | Scan the VIN code from a barcode or a text line and extract the vehicle information. |
88

9+
## Testing
10+
11+
Install dependencies
12+
13+
```
14+
cd VINScanner
15+
16+
npm install
17+
```
18+
19+
Execute playwright code coverage test by simply run
20+
21+
```
22+
npm test
23+
```
24+
925
## License
1026

1127
You can request a 30-day trial license via the [Request a Trial License](https://www.dynamsoft.com/customer/license/trialLicense/?product=cvs&utm_source=github&package=js) link.

VINScanner/.babelrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"presets": ["@babel/preset-env"],
3+
"plugins": ["istanbul"]
4+
}
5+

VINScanner/.nycrc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "@istanbuljs/nyc-config-typescript",
3+
"all": true,
4+
"check-coverage": true,
5+
"reporter": ["text", "html", "lcov"],
6+
"include": ["**/*.js"],
7+
"exclude": [
8+
"tests",
9+
"node_modules",
10+
"coverage"
11+
]
12+
}

VINScanner/css/index.css

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ body {
2525
height: 100%;
2626
-webkit-text-size-adjust: 100%;
2727
/* Prevent font scaling in landscape while allowing user zoom */
28-
background-color: #2b2b2b;
28+
overflow: hidden;
2929
}
3030

3131
button {
@@ -52,7 +52,6 @@ img {
5252
color: #ffffff;
5353
background-color: #2b2b2b;
5454
padding: 30px 0;
55-
gap: 2rem;
5655
}
5756

5857
.home-page .logo {
@@ -103,7 +102,6 @@ img {
103102
.home-page .powered-by-msg {
104103
font-size: 16px;
105104
font-family: Oswald-Light;
106-
padding-bottom: 2rem;
107105
}
108106

109107
.scanner-container {
@@ -443,10 +441,6 @@ img {
443441
}
444442

445443
@media screen and (max-width: 800px) {
446-
html,
447-
body {
448-
background-color: #323234;
449-
}
450444
.home-page {
451445
background-color: #323234;
452446
}

VINScanner/package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "vinscanner",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"test": "nyc --reporter=html --reporter=text npm run test:playwright",
7+
"test:playwright": "playwright test",
8+
"dev": "vite --host"
9+
},
10+
"keywords": [],
11+
"author": "",
12+
"license": "ISC",
13+
"description": "",
14+
"devDependencies": {
15+
"@istanbuljs/nyc-config-typescript": "^1.0.2",
16+
"@playwright/test": "^1.46.1",
17+
"babel-plugin-istanbul": "^7.0.0",
18+
"nyc": "^17.0.0",
19+
"playwright": "^1.46.1",
20+
"source-map-support": "^0.5.21",
21+
"ts-node": "^10.9.2",
22+
"vite": "^4.4.0"
23+
}
24+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { test, expect } from '../fixtures';
2+
3+
// available resolutions
4+
const availableResolutions:{width: number, height: number}[] = [
5+
{ width: 160, height: 120 },
6+
{ width: 320, height: 240 },
7+
{ width: 480, height: 360 },
8+
{ width: 640, height: 480 },
9+
{ width: 800, height: 600 },
10+
{ width: 960, height: 720 },
11+
{ width: 1280, height: 720 },
12+
{ width: 1920, height: 1080 },
13+
{ width: 2560, height: 1440 },
14+
{ width: 3840, height: 2160 },
15+
];
16+
17+
test.describe('Minimum Element Page Tests', () => {
18+
test.beforeEach(async ({ minElementPage }) => {
19+
await minElementPage.navigateTo();
20+
});
21+
22+
test('should display the correct title', async ({ minElementPage }) => {
23+
const title = await minElementPage.getTitle();
24+
expect(title).toBe("VIN Scanner - Minimum elements");
25+
});
26+
27+
test('should have camera enhancer available', async ({ minElementPage }) => {
28+
const hasEnhancer = await minElementPage.hasCameraEnhancer();
29+
expect(hasEnhancer).toBeTruthy();
30+
});
31+
32+
test('should get correct resolution', async ({ minElementPage }) => {
33+
const resolution = await minElementPage.getResolution();
34+
expect(availableResolutions).toContainEqual(resolution);
35+
});
36+
37+
test('should get all available resolutions', async ({ minElementPage }) => {
38+
const resolutions = await minElementPage.getAllResolutions();
39+
expect(resolutions).toEqual(availableResolutions);
40+
});
41+
42+
test('should be able to select different resolutions', async ({ minElementPage }) => {
43+
expect(minElementPage.selectResolution)
44+
minElementPage.selectResolution();
45+
46+
});
47+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { test, expect } from "./../fixtures";
2+
3+
test.describe("Verify the VIN Scanner Page title and veirfy user can select different settings", () => {
4+
test.beforeEach(async ({ vinScannerPage }) => {
5+
6+
// Mock the camera
7+
await vinScannerPage.grantCameraPermission();
8+
9+
// Navigate to the VIN Scanner page
10+
await vinScannerPage.navigateTo();
11+
});
12+
13+
test("should display the correct title", async ({ vinScannerPage }) => {
14+
15+
// Validate the page title
16+
const title = await vinScannerPage.getTitle();
17+
await expect(title).toContain("VIN Scanner");
18+
19+
});
20+
21+
22+
test('should click "Scan Text and Barcode" button in the settings modal and validate the header label text', async ({ vinScannerPage }) => {
23+
await vinScannerPage.clickStartButton();
24+
await vinScannerPage.interactWithSettingsModal();
25+
await vinScannerPage.clickScanBothButton();
26+
const header = await vinScannerPage.getHeaderLabel();
27+
expect(header).toBe('Scan Text or Barcode');
28+
29+
});
30+
31+
test('should click "Scan by Barcode" button in the settings modal and validate the header label text', async ({ vinScannerPage }) => {
32+
await vinScannerPage.clickStartButton();
33+
await vinScannerPage.interactWithSettingsModal();
34+
await vinScannerPage.clickScanBarcodeButton();
35+
const header = await vinScannerPage.getHeaderLabel();
36+
expect(header).toBe('Scan by Barcode');
37+
38+
});
39+
40+
test('should click "Scan by Text" button in the settings modal and validate the header label text', async ({ vinScannerPage }) => {
41+
await vinScannerPage.clickStartButton();
42+
await vinScannerPage.interactWithSettingsModal();
43+
await vinScannerPage.clickScanTextButton();
44+
const header = await vinScannerPage.getHeaderLabel();
45+
expect(header).toBe('Scan by Text');
46+
47+
});
48+
49+
});

VINScanner/tests/fixtures.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { test as baseTest } from '@playwright/test';
2+
import { MinElementPage } from './pages/MinElementPage';
3+
import { VinScannerPage } from './pages/VinScannerPage';
4+
5+
type MyFixtures = {
6+
minElementPage: MinElementPage;
7+
vinScannerPage: VinScannerPage;
8+
};
9+
10+
export const test = baseTest.extend<MyFixtures>({
11+
12+
13+
minElementPage: async ({ page }, use) => {
14+
const minElementPage = new MinElementPage(page);
15+
await use(minElementPage);
16+
},
17+
vinScannerPage: async ({ page }, use) => {
18+
const vinScannerPage = new VinScannerPage(page);
19+
await use(vinScannerPage);
20+
},
21+
22+
});
23+
24+
export { expect } from '@playwright/test';

VINScanner/tests/global.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare var cameraEnhancer: {
2+
getResolution: () => { width: number; height: number };
3+
getAvailableResolutions: () => Promise<{ width: number; height: number }[]>;
4+
};
5+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Page, Locator } from '@playwright/test';
2+
3+
// TODO: Update the URL when we upload the page to live server.
4+
// const URL = "https://demo.dynamsoft.com/Samples/DBR/JS/hello-world/hello-world.html";
5+
const URL = "http://localhost:5173/minimum-elements.html";
6+
7+
export class MinElementPage {
8+
private page: Page;
9+
private selResolution: Locator;
10+
private options: Locator[];
11+
12+
constructor(page: Page) {
13+
this.page = page;
14+
this.selResolution = this.page.locator('select.dce-sel-resolution');
15+
}
16+
17+
async initialize() {
18+
this.options = await this.selResolution.locator('option').all();
19+
}
20+
21+
async grantCameraPermission() {
22+
await this.page.addScriptTag({
23+
content: `
24+
navigator.mediaDevices.getUserMedia = async () => {
25+
return {
26+
getVideoTracks: () => [{
27+
applyConstraints: () => {},
28+
stop: () => {},
29+
}],
30+
getAudioTracks: () => [],
31+
};
32+
};
33+
`,
34+
});
35+
}
36+
37+
async navigateTo() {
38+
await this.grantCameraPermission();
39+
await this.page.goto(URL);
40+
await this.initialize();
41+
}
42+
43+
async getTitle() {
44+
return await this.page.title();
45+
}
46+
47+
async hasCameraEnhancer() {
48+
const camExists = await this.page.waitForFunction(() => typeof Dynamsoft.DCE.cameraEnhancer !== "undefined", { timeout: 5000 });
49+
return camExists;
50+
}
51+
52+
async getResolution() {
53+
await this.hasCameraEnhancer();
54+
const res = await this.page.evaluate(() => {
55+
return cameraEnhancer.getResolution();
56+
});
57+
return { width: res.width, height: res.height };
58+
}
59+
60+
async getAllResolutions() {
61+
await this.hasCameraEnhancer();
62+
let availableResolutions: { width: number; height: number; }[] | null = null;
63+
const maxAttempts = 10;
64+
let attempts = 0;
65+
const delay = 500;
66+
67+
while (attempts < maxAttempts && !availableResolutions) {
68+
availableResolutions = await this.page.evaluate(async () => {
69+
if (typeof cameraEnhancer !== "undefined" && typeof cameraEnhancer.getAvailableResolutions === "function") {
70+
const resolutions = await cameraEnhancer.getAvailableResolutions();
71+
return resolutions && resolutions.length > 0 ? resolutions : null;
72+
}
73+
return null;
74+
});
75+
76+
if (!availableResolutions) {
77+
await new Promise(resolve => setTimeout(resolve, delay));
78+
}
79+
80+
attempts++;
81+
}
82+
return availableResolutions;
83+
}
84+
85+
async getCurrentResolution() {
86+
return this.selResolution.getAttribute
87+
}
88+
async selectResolution() {
89+
for (const res of this.options) {
90+
this.selResolution.click();
91+
res.click();
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)