diff --git a/.gitignore b/.gitignore index 62bcc74..757056b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ delete.bat model/build/Mask* model/build/taco* tags +frontend/yarn.lock diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b963eab..d40a432 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,7 +1,7 @@ # Dockerfile for trash-ai frontend # Stage 1: Build the project -FROM node:16.12.0 as builder +FROM node:16.20.0 as builder # Set the working directory WORKDIR /app diff --git a/frontend/README.md b/frontend/README.md index b9c5255..4a962c5 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -20,5 +20,10 @@ yarn build yarn lint ``` +### Run Cypress Tests +``` +yarn run cypress open +``` + ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/frontend/__tests__/README.md b/frontend/__tests__/README.md new file mode 100644 index 0000000..38d9652 --- /dev/null +++ b/frontend/__tests__/README.md @@ -0,0 +1,62 @@ +# Tests and Verifications + +## Automatic Tests + +### Scenarios +1. Does the landing pag load? +2. On hitting show samples, does processing complete in an expected amount of time? Does it show all the results expected? +3. After recieving results, can you click an image and does the page show the expected data/metadata/etc? +4. After recieving results, does the summary page contain the correct data? + +## How to run + +1. Navigate to `frontend/` +2. Run `yarn install` +2. Spin up docker container +`docker-compose up` +3. Run the auto tests +`yarn run jest` + +## Manual Tests + +1. Does the landing pag load? + +When you open the landing page. Either at https://www.trashai.org/ or http://localhost:5150 + +The page should loads in a reasonable timeframe. (<2 seconds) + +*You can use the `Network` tab in the dev tools for more fine grained analysis.* + +2. On hitting show samples, does processing complete in an expected amount of time? Does it show all the results expected? + +When the show samples button is pressed. (as depicted below) +![Instructions](misc/Show_Samples_Button.png "Show Title Button") + +Then the app will start processing images. (as depcited below) +![Processing](misc/Processing_Samples.png "Processing Samples Progress Bar") + +*The processing should be relatively quick, it it takes more than a minute, something is definetly wrong.* + +When the processing is done, the results will be available in the summary page. + +![Summary ](misc/Summary_Of_Samples.png "Summary of Sample Results") + +It should be populated like above. + +3. After recieving results, can you click an image and does the page show the expected data/metadata/etc? + +When one of the links in the summary table is clicked. + +![Open images](misc/Open_images.png "Open Images") + +Then the images will be displayed along with the segmentation and classes detected. + +![List Images](misc/Images_with_Segmentation.png "List Images") + +When the METADATA tab is opened, the relevant metadata for the image is displayed. + +![View Metadata](misc/Viewing_Metadata.png "View Metadata") + +4. After recieving results, does the summary page contain the correct data? + +*The summary page shows the results of processing a random sample of images, as long as it's not empty it can be considered as correct.* \ No newline at end of file diff --git a/frontend/__tests__/misc/Images_with_Segmentation.png b/frontend/__tests__/misc/Images_with_Segmentation.png new file mode 100644 index 0000000..96e8edb Binary files /dev/null and b/frontend/__tests__/misc/Images_with_Segmentation.png differ diff --git a/frontend/__tests__/misc/Open_images.png b/frontend/__tests__/misc/Open_images.png new file mode 100644 index 0000000..e80a1e3 Binary files /dev/null and b/frontend/__tests__/misc/Open_images.png differ diff --git a/frontend/__tests__/misc/Processing_Samples.png b/frontend/__tests__/misc/Processing_Samples.png new file mode 100644 index 0000000..87a9914 Binary files /dev/null and b/frontend/__tests__/misc/Processing_Samples.png differ diff --git a/frontend/__tests__/misc/README.md b/frontend/__tests__/misc/README.md new file mode 100644 index 0000000..ac211c0 --- /dev/null +++ b/frontend/__tests__/misc/README.md @@ -0,0 +1 @@ +*placeholder* \ No newline at end of file diff --git a/frontend/__tests__/misc/Show_Samples_Button.png b/frontend/__tests__/misc/Show_Samples_Button.png new file mode 100644 index 0000000..c0fa5d5 Binary files /dev/null and b/frontend/__tests__/misc/Show_Samples_Button.png differ diff --git a/frontend/__tests__/misc/Summary_Of_Samples.png b/frontend/__tests__/misc/Summary_Of_Samples.png new file mode 100644 index 0000000..85b4259 Binary files /dev/null and b/frontend/__tests__/misc/Summary_Of_Samples.png differ diff --git a/frontend/__tests__/misc/Viewing_Metadata.png b/frontend/__tests__/misc/Viewing_Metadata.png new file mode 100644 index 0000000..82f37ad Binary files /dev/null and b/frontend/__tests__/misc/Viewing_Metadata.png differ diff --git a/frontend/__tests__/smokeTest.js b/frontend/__tests__/smokeTest.js new file mode 100644 index 0000000..6b35441 --- /dev/null +++ b/frontend/__tests__/smokeTest.js @@ -0,0 +1,110 @@ +/* eslint-disable no-undef */ +const timeout = 12000 +const url = 'http://localhost:5150/' + +describe( + 'Smoke test for Trash-AI', + () => { + let page + beforeAll(async () => { + page = await global.__BROWSER__.newPage() + await page.setViewport({ + width: 1536, + height: 960, + }) + await page.goto(url) + }, timeout) + + afterAll(async () => { + await page.close() + }) + + it('should load without error', async () => { + const text = await page.evaluate(() => document.body.textContent) + expect(text).toContain('Trash AI') + }) + + it( + 'should show samples when prompted', + async () => { + // Open actions menu + const openActionsBtn = await page.waitForSelector( + '#actions-button-test-id', + ) + await openActionsBtn.click() + await page.waitForTimeout(1000) + + // Click Show Samples + const showSamplesBtn = await page.waitForSelector( + '#show-samples-test-id', + ) + await showSamplesBtn.click() + await page.waitForTimeout(2000) + + // Navigate to Summary page + const summaryBtn = await page.waitForSelector( + '#summary-tab-test-id', + ) + await summaryBtn.click() + await page.waitForTimeout(500) + + // Check if navigations was successful + current_url = await page.url() + expect(current_url).toBe(`${url}summary/detections`) + + // Check if results are displayed + const drinkCanImg = await page.waitForSelector( + 'a[href="/detection/Drink%20can"]', + ) + await drinkCanImg.click() + await page.waitForTimeout(500) + + // Check if navigation happened + current_url = await page.url() + expect(current_url).toBe(`${url}detection/Drink%20can`) + + // Navigate to image view page + const sampleImg = await page.waitForSelector( + 'div[id="sample01.jpg-test-id"]', + ) + await sampleImg.click() + await page.waitForTimeout(500) + + // Check if navigation happened + current_url = await page.url() + expect(current_url).toBe(`${url}image/0/image`) + + // Check if the image is loaded + const canvasImg = await page.waitForSelector('#canvasparent') + + // Check of the images classes are loaded + const drinkCanClass = await page.waitForSelector( + 'a[href="/detection/Drink%20can"]', + ) + + const paperCupClass = await page.waitForSelector( + 'a[href="/detection/Paper%20cup"]', + ) + + // Check of the image and metadata tabs are loaded + const imageTab = await page.waitForSelector( + '#image-tab-test-id', + ) + + const metadataTab = await page.waitForSelector( + '#meta-tab-test-id', + ) + metadataTab.click() + await page.waitForTimeout(500) + + // Check is exif data is displayed + const exifData = await page.waitForSelector('#exifData-test-id') + // Check if metadata is displayed + const metaData = await page.waitForSelector('#metaData-test-id') + await page.waitForTimeout(500) + }, + timeout, + ) + }, + timeout, +) diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts new file mode 100644 index 0000000..25b1372 --- /dev/null +++ b/frontend/cypress.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + baseUrl: "https://www.trashai.org/", + pageLoadTimeout: 3000 + }, +}); diff --git a/frontend/jest.config.js b/frontend/jest.config.js new file mode 100644 index 0000000..c03f39d --- /dev/null +++ b/frontend/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + globalSetup: './setup.js', + globalTeardown: './teardown.js', + testEnvironment: './puppeteer_environment.js', +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 3fab95d..9960848 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,8 @@ "serve": "vite preview", "build": "vite build", "lint": "vue-cli-service lint", - "dev": "vite" + "dev": "vite", + "test": "jest" }, "dependencies": { "@fawmi/vue-google-maps": "^0.9.72", @@ -23,11 +24,14 @@ "dexie": "^3.2.2", "file-saver": "^2.0.5", "fuse.js": "^6.6.2", + "jest": "^29.5.0", "jszip": "^3.10.0", "loglevel": "^1.8.0", "p-queue": "^7.3.0", "pinia": "^2.0.17", "pinia-plugin-persistedstate": "^1.6.3", + "puppeteer": "^20.7.1", + "rimraf": "^5.0.1", "roboto-fontface": "^0.10.0", "rxjs": "^7.5.7", "ts-exif-parser": "^0.2.2", @@ -56,6 +60,7 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-vue": "^9.2.0", + "jest-puppeteer": "^9.0.0", "prettier": "^2.7.1", "sass": "^1.53.0", "typescript": "^4.7.4", diff --git a/frontend/puppeteer_environment.js b/frontend/puppeteer_environment.js new file mode 100644 index 0000000..b5c0aa8 --- /dev/null +++ b/frontend/puppeteer_environment.js @@ -0,0 +1,36 @@ +import NodeEnvironment from 'jest-environment-node' +import puppeteer from 'puppeteer' +import fs from 'fs' +import os from 'os' +import path from 'path' + +const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup') + +class PuppeteerEnvironment extends NodeEnvironment { + constructor(config) { + super(config) + } + + async setup() { + console.log('Setup Test Environment.') + await super.setup() + const wsEndpoint = fs.readFileSync(path.join(DIR, 'wsEndpoint'), 'utf8') + if (!wsEndpoint) { + throw new Error('wsEndpoint not found') + } + this.global.__BROWSER__ = await puppeteer.connect({ + browserWSEndpoint: wsEndpoint, + }) + } + + async teardown() { + console.log('Teardown Test Environment.') + await super.teardown() + } + + runScript(script) { + return super.runScript(script) + } +} + +module.exports = PuppeteerEnvironment diff --git a/frontend/setup.js b/frontend/setup.js new file mode 100644 index 0000000..f5abeeb --- /dev/null +++ b/frontend/setup.js @@ -0,0 +1,23 @@ +import puppeteer from 'puppeteer' +import fs from 'fs' +import mkdirp from 'mkdirp' +import os from 'os' +import path from 'path' + +const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup') + +module.exports = async function () { + console.log('Setup Puppeteer') + const browser = await puppeteer.launch({ + headless: false, + defaultViewport: null, + args: ['--window-size=1920,1080'], + }) + // This global is not available inside tests but only in global teardown + global.__BROWSER_GLOBAL__ = browser + // Instead, we expose the connection details via file system to be used in tests + mkdirp.sync(DIR) + + console.log(`DIR ${DIR}`) + fs.writeFileSync(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint()) +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 66d1cce..ad4f13d 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -16,6 +16,7 @@ @@ -98,12 +99,13 @@ @click="$router.push({ name: 'about' })" :active="isactive('about')" > - + mdi-information About - + mdi-book-open-variant diff --git a/frontend/src/components/model/thumb.vue b/frontend/src/components/model/thumb.vue index 737ba30..70c39b2 100644 --- a/frontend/src/components/model/thumb.vue +++ b/frontend/src/components/model/thumb.vue @@ -1,6 +1,9 @@ +