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 @@
+
-
-
+
+
{{ item.filename }}
@@ -26,6 +27,7 @@