Skip to content

Commit

Permalink
feat(VINScanner): allow vertical scan on barcode mode (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
felixindynamsoft authored and felixindrawan committed Sep 6, 2024
1 parent caaa0e5 commit c08e588
Show file tree
Hide file tree
Showing 17 changed files with 626 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
with:
name: playwright-report
path: playwright-report/
retention-days: 30
retention-days: 30
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ 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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
| ------------ | ------------------------------------------------------------------------------------ |
| `VIN Scanner` | Scan the VIN code from a barcode or a text line and extract the vehicle information. |

## Testing

Install dependencies

```
cd VINScanner
npm install
```

Execute playwright code coverage test by simply run

```
npm test
```

## License

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.
Expand Down
5 changes: 5 additions & 0 deletions VINScanner/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["istanbul"]
}

12 changes: 12 additions & 0 deletions VINScanner/.nycrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
"all": true,
"check-coverage": true,
"reporter": ["text", "html", "lcov"],
"include": ["**/*.js"],
"exclude": [
"tests",
"node_modules",
"coverage"
]
}
8 changes: 1 addition & 7 deletions VINScanner/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ body {
height: 100%;
-webkit-text-size-adjust: 100%;
/* Prevent font scaling in landscape while allowing user zoom */
background-color: #2b2b2b;
overflow: hidden;
}

button {
Expand All @@ -52,7 +52,6 @@ img {
color: #ffffff;
background-color: #2b2b2b;
padding: 30px 0;
gap: 2rem;
}

.home-page .logo {
Expand Down Expand Up @@ -103,7 +102,6 @@ img {
.home-page .powered-by-msg {
font-size: 16px;
font-family: Oswald-Light;
padding-bottom: 2rem;
}

.scanner-container {
Expand Down Expand Up @@ -443,10 +441,6 @@ img {
}

@media screen and (max-width: 800px) {
html,
body {
background-color: #323234;
}
.home-page {
background-color: #323234;
}
Expand Down
24 changes: 24 additions & 0 deletions VINScanner/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "vinscanner",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "nyc --reporter=html --reporter=text npm run test:playwright",
"test:playwright": "playwright test",
"dev": "vite --host"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@playwright/test": "^1.46.1",
"babel-plugin-istanbul": "^7.0.0",
"nyc": "^17.0.0",
"playwright": "^1.46.1",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.2",
"vite": "^4.4.0"
}
}
47 changes: 47 additions & 0 deletions VINScanner/tests/e2e/test-MinElement.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { test, expect } from '../fixtures';

// available resolutions
const availableResolutions:{width: number, height: number}[] = [
{ width: 160, height: 120 },
{ width: 320, height: 240 },
{ width: 480, height: 360 },
{ width: 640, height: 480 },
{ width: 800, height: 600 },
{ width: 960, height: 720 },
{ width: 1280, height: 720 },
{ width: 1920, height: 1080 },
{ width: 2560, height: 1440 },
{ width: 3840, height: 2160 },
];

test.describe('Minimum Element Page Tests', () => {
test.beforeEach(async ({ minElementPage }) => {
await minElementPage.navigateTo();
});

test('should display the correct title', async ({ minElementPage }) => {
const title = await minElementPage.getTitle();
expect(title).toBe("VIN Scanner - Minimum elements");
});

test('should have camera enhancer available', async ({ minElementPage }) => {
const hasEnhancer = await minElementPage.hasCameraEnhancer();
expect(hasEnhancer).toBeTruthy();
});

test('should get correct resolution', async ({ minElementPage }) => {
const resolution = await minElementPage.getResolution();
expect(availableResolutions).toContainEqual(resolution);
});

test('should get all available resolutions', async ({ minElementPage }) => {
const resolutions = await minElementPage.getAllResolutions();
expect(resolutions).toEqual(availableResolutions);
});

test('should be able to select different resolutions', async ({ minElementPage }) => {
expect(minElementPage.selectResolution)
minElementPage.selectResolution();

});
});
49 changes: 49 additions & 0 deletions VINScanner/tests/e2e/test-VinScanner.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { test, expect } from "./../fixtures";

test.describe("Verify the VIN Scanner Page title and veirfy user can select different settings", () => {
test.beforeEach(async ({ vinScannerPage }) => {

// Mock the camera
await vinScannerPage.grantCameraPermission();

// Navigate to the VIN Scanner page
await vinScannerPage.navigateTo();
});

test("should display the correct title", async ({ vinScannerPage }) => {

// Validate the page title
const title = await vinScannerPage.getTitle();
await expect(title).toContain("VIN Scanner");

});


test('should click "Scan Text and Barcode" button in the settings modal and validate the header label text', async ({ vinScannerPage }) => {
await vinScannerPage.clickStartButton();
await vinScannerPage.interactWithSettingsModal();
await vinScannerPage.clickScanBothButton();
const header = await vinScannerPage.getHeaderLabel();
expect(header).toBe('Scan Text or Barcode');

});

test('should click "Scan by Barcode" button in the settings modal and validate the header label text', async ({ vinScannerPage }) => {
await vinScannerPage.clickStartButton();
await vinScannerPage.interactWithSettingsModal();
await vinScannerPage.clickScanBarcodeButton();
const header = await vinScannerPage.getHeaderLabel();
expect(header).toBe('Scan by Barcode');

});

test('should click "Scan by Text" button in the settings modal and validate the header label text', async ({ vinScannerPage }) => {
await vinScannerPage.clickStartButton();
await vinScannerPage.interactWithSettingsModal();
await vinScannerPage.clickScanTextButton();
const header = await vinScannerPage.getHeaderLabel();
expect(header).toBe('Scan by Text');

});

});
24 changes: 24 additions & 0 deletions VINScanner/tests/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test as baseTest } from '@playwright/test';
import { MinElementPage } from './pages/MinElementPage';
import { VinScannerPage } from './pages/VinScannerPage';

type MyFixtures = {
minElementPage: MinElementPage;
vinScannerPage: VinScannerPage;
};

export const test = baseTest.extend<MyFixtures>({


minElementPage: async ({ page }, use) => {
const minElementPage = new MinElementPage(page);
await use(minElementPage);
},
vinScannerPage: async ({ page }, use) => {
const vinScannerPage = new VinScannerPage(page);
await use(vinScannerPage);
},

});

export { expect } from '@playwright/test';
5 changes: 5 additions & 0 deletions VINScanner/tests/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare var cameraEnhancer: {
getResolution: () => { width: number; height: number };
getAvailableResolutions: () => Promise<{ width: number; height: number }[]>;
};

94 changes: 94 additions & 0 deletions VINScanner/tests/pages/MinElementPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Page, Locator } from '@playwright/test';

// TODO: Update the URL when we upload the page to live server.
// const URL = "https://demo.dynamsoft.com/Samples/DBR/JS/hello-world/hello-world.html";
const URL = "http://localhost:5173/minimum-elements.html";

export class MinElementPage {
private page: Page;
private selResolution: Locator;
private options: Locator[];

constructor(page: Page) {
this.page = page;
this.selResolution = this.page.locator('select.dce-sel-resolution');
}

async initialize() {
this.options = await this.selResolution.locator('option').all();
}

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

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

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

async hasCameraEnhancer() {
const camExists = await this.page.waitForFunction(() => typeof Dynamsoft.DCE.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.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") {
const resolutions = await cameraEnhancer.getAvailableResolutions();
return resolutions && resolutions.length > 0 ? resolutions : null;
}
return null;
});

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

attempts++;
}
return availableResolutions;
}

async getCurrentResolution() {
return this.selResolution.getAttribute
}
async selectResolution() {
for (const res of this.options) {
this.selResolution.click();
res.click();
}
}
}
Loading

0 comments on commit c08e588

Please sign in to comment.