diff --git a/003_test/.editorconfig b/003_test/.editorconfig new file mode 100644 index 0000000..0f17867 --- /dev/null +++ b/003_test/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/003_test/.gitignore b/003_test/.gitignore new file mode 100644 index 0000000..da9d21f --- /dev/null +++ b/003_test/.gitignore @@ -0,0 +1,58 @@ +**/*del +**/*bak +**/*log +**/*tmp + +.env +.env.production + +**/*.draft +**/~* +**/*copy*.tsx +**/*copy.tsx + +# **/repomix-output.xml +**/*:Zone.Identifier +**/*.bak +**/*.log +**/*.tmp +**/*.del +**/*.plan +**/_archive + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem +.swc + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/003_test/.vscode/extensions.json b/003_test/.vscode/extensions.json new file mode 100644 index 0000000..f5b1ea0 --- /dev/null +++ b/003_test/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "foxundermoon.shell-format", + "redhat.vscode-yaml" + ] +} diff --git a/003_test/.vscode/settings.json b/003_test/.vscode/settings.json new file mode 100644 index 0000000..27e8432 --- /dev/null +++ b/003_test/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "[dockerfile]": { + "editor.quickSuggestions": { + "strings": true + }, + "editor.defaultFormatter": "foxundermoon.shell-format" + } +} diff --git a/003_test/001_desktop/01_test_seat/app/.editorconfig b/003_test/001_desktop/01_test_seat/app/.editorconfig new file mode 100644 index 0000000..0f17867 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/003_test/001_desktop/01_test_seat/app/.gitignore b/003_test/001_desktop/01_test_seat/app/.gitignore new file mode 100644 index 0000000..58786aa --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/.gitignore @@ -0,0 +1,7 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/003_test/001_desktop/01_test_seat/app/.prettierrc b/003_test/001_desktop/01_test_seat/app/.prettierrc new file mode 100644 index 0000000..e18b0cf --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/.prettierrc @@ -0,0 +1,10 @@ +{ + "endOfLine": "lf", + "printWidth": 120, + "quoteProps": "consistent", + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "plugins": [] +} diff --git a/003_test/001_desktop/01_test_seat/app/package-lock.json b/003_test/001_desktop/01_test_seat/app/package-lock.json new file mode 100644 index 0000000..b9d7772 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/package-lock.json @@ -0,0 +1,89 @@ +{ + "name": "app", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "app", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "prettier": "^3.5.3" + }, + "devDependencies": { + "@playwright/test": "^1.52.0", + "@types/node": "^22.15.18" + } + }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.15.18", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/playwright": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + } + } +} diff --git a/003_test/001_desktop/01_test_seat/app/package.json b/003_test/001_desktop/01_test_seat/app/package.json new file mode 100644 index 0000000..ed00847 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/package.json @@ -0,0 +1,17 @@ +{ + "name": "app", + "version": "1.0.0", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@playwright/test": "^1.52.0", + "@types/node": "^22.15.18" + }, + "dependencies": { + "prettier": "^3.5.3" + } +} diff --git a/003_test/README.md b/003_test/001_desktop/01_test_seat/app/parking/_GUIDELINE.md similarity index 100% rename from 003_test/README.md rename to 003_test/001_desktop/01_test_seat/app/parking/_GUIDELINE.md diff --git a/003_test/001_desktop/01_test_seat/app/parking/example.spec.ts b/003_test/001_desktop/01_test_seat/app/parking/example.spec.ts new file mode 100644 index 0000000..54a906a --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/parking/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); diff --git a/003_test/001_desktop/01_test_seat/app/parking/helloworld.spec.ts.demo b/003_test/001_desktop/01_test_seat/app/parking/helloworld.spec.ts.demo new file mode 100644 index 0000000..e77d52a --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/parking/helloworld.spec.ts.demo @@ -0,0 +1,30 @@ +import { test, expect } from '@playwright/test'; +import { HELLO } from '../_config/helloworld'; + +test('fresh user should appears sign in page', async ({ page }) => { + await page.goto('http://192.168.222.199:3000/dashboard'); + + console.log({ HELLO }); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Sign in | Custom | Auth | demo cms/); +}); + +// test('fresh user login', async ({ page }) => { +// await page.goto('http://192.168.222.199:3000/dashboard'); + +// // Expect a title "to contain" a substring. +// const emailField = page.getByPlaceholder('e.g. admin@123.com'); + +// await emailField.press('Enter'); +// }); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); diff --git a/003_test/001_desktop/01_test_seat/app/playwright.config.ts b/003_test/001_desktop/01_test_seat/app/playwright.config.ts new file mode 100644 index 0000000..f7c56f3 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/003_test/001_desktop/01_test_seat/app/run.sh b/003_test/001_desktop/01_test_seat/app/run.sh new file mode 100755 index 0000000..d9e9233 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/run.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -ex + +# npx playwright test +npx playwright --version + +npx playwright test --ui + +npx playwright show-report --host 0.0.0.0 + +echo "done" diff --git a/003_test/001_desktop/01_test_seat/app/scripts/001_setup.sh b/003_test/001_desktop/01_test_seat/app/scripts/001_setup.sh new file mode 100755 index 0000000..3bd6987 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/scripts/001_setup.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -ex + +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash + +echo "done" diff --git a/003_test/001_desktop/01_test_seat/app/scripts/002_setup_playwright.sh b/003_test/001_desktop/01_test_seat/app/scripts/002_setup_playwright.sh new file mode 100755 index 0000000..e8cfc3f --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/scripts/002_setup_playwright.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -ex + +npx playwright install +npx playwright install-deps + +echo "done" diff --git a/003_test/001_desktop/01_test_seat/app/scripts/003_google_chrome.sh b/003_test/001_desktop/01_test_seat/app/scripts/003_google_chrome.sh new file mode 100755 index 0000000..b8dcfe7 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/scripts/003_google_chrome.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +echo "**** install chrome ****" +apt update +apt install -y wget +wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - +echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | tee /etc/apt/sources.list.d/google-chrome.list +apt update +apt install -y google-chrome-stable + +echo "install chrome done" diff --git a/003_test/001_desktop/01_test_seat/app/scripts/run_all.sh b/003_test/001_desktop/01_test_seat/app/scripts/run_all.sh new file mode 100755 index 0000000..a3e038e --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/scripts/run_all.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -ex + +cd scripts +./001_setup.sh 2>&1 | tee setup.log +./002_setup_playwright.sh 2>&1 | tee setup.log +sudo ./003_google_chrome.sh 2>&1 | tee setup.log + +cd .. + +echo "done" diff --git a/003_test/001_desktop/01_test_seat/app/tests-examples/demo-todo-app.spec.ts b/003_test/001_desktop/01_test_seat/app/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..c25eeb1 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,416 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = ['buy some cheese', 'feed the cat', 'book a doctors appointment'] as const; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([TODO_ITEMS[0]]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count'); + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect( + todoItem.locator('label', { + hasText: TODO_ITEMS[1], + }) + ).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count'); + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction((e) => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction((e) => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction((t) => { + return JSON.parse(localStorage['react-todos']) + .map((todo: any) => todo.title) + .includes(t); + }, title); +} diff --git a/003_test/001_desktop/01_test_seat/app/tests/REQ0016/001_fresh-user-should-appears-sign-in-page.spec.ts b/003_test/001_desktop/01_test_seat/app/tests/REQ0016/001_fresh-user-should-appears-sign-in-page.spec.ts new file mode 100644 index 0000000..7c605d2 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/tests/REQ0016/001_fresh-user-should-appears-sign-in-page.spec.ts @@ -0,0 +1,32 @@ +// tests/REQ0006/001_fresh-user-should-appears-sign-in-page.spec.ts +import { test, expect } from '@playwright/test'; +// +import { CMS_HOST, HELLO } from '../_config/helloworld'; +import { TS_HELLO } from '../_test_set/helloworld'; + +test('fresh user should appears sign in page', async ({ page }) => { + console.log({ HELLO, TS_HELLO }); + await page.goto(`${CMS_HOST}/dashboard`); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Sign in | Custom | Auth | demo cms/); +}); + +// test('fresh user login', async ({ page }) => { +// await page.goto('http://192.168.222.199:3000/dashboard'); + +// // Expect a title "to contain" a substring. +// const emailField = page.getByPlaceholder('e.g. admin@123.com'); + +// await emailField.press('Enter'); +// }); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); diff --git a/003_test/001_desktop/01_test_seat/app/tests/REQ0016/002_user-login.spec.ts b/003_test/001_desktop/01_test_seat/app/tests/REQ0016/002_user-login.spec.ts new file mode 100644 index 0000000..6e3f699 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/tests/REQ0016/002_user-login.spec.ts @@ -0,0 +1,40 @@ +// tests/REQ0006/001_fresh-user-should-appears-sign-in-page.spec.ts +import { test, expect } from '@playwright/test'; +// +import { CMS_HOST, HELLO } from '../_config/helloworld'; +import { TS_HELLO } from '../_test_set/helloworld'; + +test('user login', async ({ page }) => { + console.log({ HELLO, TS_HELLO }); + + await page.goto(`${CMS_HOST}/dashboard`); + await expect(page).toHaveTitle(/Sign in | Custom | Auth | demo cms/); + + await page.getByPlaceholder('name@example.com').pressSequentially('user5@123.com'); + await page.getByPlaceholder('password').pressSequentially('user5@123.com'); + await page.waitForTimeout(1 * 1000); + + await page.getByRole('button', { name: 'Sign in' }).click(); + await page.waitForTimeout(1 * 1000); + + await expect(page).toHaveTitle(/192.168.222.199:3000\/dashboard/); +}); + +// test('fresh user login', async ({ page }) => { +// await page.goto('http://192.168.222.199:3000/dashboard'); + +// // Expect a title "to contain" a substring. +// const emailField = page.getByPlaceholder('e.g. admin@123.com'); + +// await emailField.press('Enter'); +// }); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); diff --git a/003_test/001_desktop/01_test_seat/app/tests/_config/helloworld.ts b/003_test/001_desktop/01_test_seat/app/tests/_config/helloworld.ts new file mode 100644 index 0000000..cfa3625 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/tests/_config/helloworld.ts @@ -0,0 +1,2 @@ +export const HELLO = 'WORLD'; +export const CMS_HOST = 'http://192.168.222.199:3000'; diff --git a/003_test/001_desktop/01_test_seat/app/tests/_test_set/helloworld.ts b/003_test/001_desktop/01_test_seat/app/tests/_test_set/helloworld.ts new file mode 100644 index 0000000..17efef4 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/app/tests/_test_set/helloworld.ts @@ -0,0 +1 @@ +export const TS_HELLO = 'WORLD'; diff --git a/003_test/001_desktop/01_test_seat/dc_up.sh b/003_test/001_desktop/01_test_seat/dc_up.sh new file mode 100755 index 0000000..0f56a7c --- /dev/null +++ b/003_test/001_desktop/01_test_seat/dc_up.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -ex + +# docker compose build | tee dc_build.log + +# docker compose kill +# docker compose down +docker compose up -d + +echo "done" diff --git a/003_test/001_desktop/01_test_seat/docker-compose.yml b/003_test/001_desktop/01_test_seat/docker-compose.yml new file mode 100644 index 0000000..7347f21 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/docker-compose.yml @@ -0,0 +1,32 @@ +name: 001_desktop + +services: + chromium: + build: . + security_opt: + - seccomp:unconfined #optional + environment: + - TITLE=desktop-test-seat-1 + - FM_HOME=/app + - PUID=1000 # default 911 + - PGID=1000 # default 911 + - TZ=Etc/Asia_HongKong + - CHROME_CLI=https://www.gmail.com/ #optional + # - CUSTOM_PORT=3000 # Internal port the container listens on for http if it needs to be swapped from the default 3000. + - CUSTOM_HTTPS_PORT=3001 # Internal port the container listens on for https if it needs to be swapped from the default 3001. + - NO_FULL=1 # Do not autmatically fullscreen applications when using openbox. + - LC_ALL=en_US.UTF-8 # Set the locale. + - INSTALL_PACKAGES=fonts-noto-cjk + ports: + # - 6000:3000 + - 6001:3001 # https vnc ip + - 9323:9323 # report ip + volumes: + - ./app:/app + shm_size: '1gb' + deploy: + resources: + limits: + cpus: 1 + reservations: + cpus: 0.01 diff --git a/003_test/001_desktop/01_test_seat/dockerfile b/003_test/001_desktop/01_test_seat/dockerfile new file mode 100644 index 0000000..03cd69d --- /dev/null +++ b/003_test/001_desktop/01_test_seat/dockerfile @@ -0,0 +1,27 @@ +FROM ghcr.io/linuxserver/baseimage-kasmvnc:ubuntujammy + +# set version label +ARG BUILD_DATE +ARG VERSION +LABEL build_version="Linuxserver.io version:- ${VERSION} Build-date:- ${BUILD_DATE}" +LABEL maintainer="thelamer" +ARG DEBIAN_FRONTEND="noninteractive" + +# title +ENV TITLE=test-seat + +RUN rm /bin/sh && ln -s /bin/bash /bin/sh + +RUN apt-get update +RUN apt-get install -qqy wget git curl + +# Set up the working directory +WORKDIR /app + +# ports and volumes +EXPOSE 3000 + +VOLUME /config + +COPY ./setup/ /setup +RUN chmod +x /setup/*.sh diff --git a/003_test/001_desktop/01_test_seat/setup/001_nvm.sh b/003_test/001_desktop/01_test_seat/setup/001_nvm.sh new file mode 100644 index 0000000..cd4ef2b --- /dev/null +++ b/003_test/001_desktop/01_test_seat/setup/001_nvm.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -e + +echo "**** install nvm ****" + +# Download and install Node.js (you may need to restart the terminal) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash + +source ~/.nvm/nvm.sh + +nvm install 20 + +nvm alias default 20 + +nvm use default + +npm install -g yarn + +echo "source ~/.nvm/nvm.sh" >>~/.bashrc + +echo "install nvm done" diff --git a/003_test/001_desktop/01_test_seat/setup/run_all.sh b/003_test/001_desktop/01_test_seat/setup/run_all.sh new file mode 100644 index 0000000..d418834 --- /dev/null +++ b/003_test/001_desktop/01_test_seat/setup/run_all.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -ex + +# ls -1 /setup/0*.sh | while read script; do +# echo "Running $script" +# bash "$script" & +# done + +/setup/001_nvm.sh +sudo /setup/002_google_chrome.sh + +wait + +echo "done" diff --git a/003_test/001_desktop/_GUIDELINES.md b/003_test/001_desktop/_GUIDELINES.md new file mode 100644 index 0000000..c26b0c9 --- /dev/null +++ b/003_test/001_desktop/_GUIDELINES.md @@ -0,0 +1,7 @@ +# GUIDELINES + +this folder contains test and validation of the project + +## highlight + +- `01_test_seat` testing for desktop diff --git a/003_test/002_mobile/01_test_seat/app/.editorconfig b/003_test/002_mobile/01_test_seat/app/.editorconfig new file mode 100644 index 0000000..0f17867 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/003_test/002_mobile/01_test_seat/app/.gitignore b/003_test/002_mobile/01_test_seat/app/.gitignore new file mode 100644 index 0000000..58786aa --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/.gitignore @@ -0,0 +1,7 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/003_test/002_mobile/01_test_seat/app/.prettierrc b/003_test/002_mobile/01_test_seat/app/.prettierrc new file mode 100644 index 0000000..e18b0cf --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/.prettierrc @@ -0,0 +1,10 @@ +{ + "endOfLine": "lf", + "printWidth": 120, + "quoteProps": "consistent", + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "plugins": [] +} diff --git a/003_test/002_mobile/01_test_seat/app/package-lock.json b/003_test/002_mobile/01_test_seat/app/package-lock.json new file mode 100644 index 0000000..b9d7772 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/package-lock.json @@ -0,0 +1,89 @@ +{ + "name": "app", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "app", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "prettier": "^3.5.3" + }, + "devDependencies": { + "@playwright/test": "^1.52.0", + "@types/node": "^22.15.18" + } + }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.15.18", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/playwright": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + } + } +} diff --git a/003_test/002_mobile/01_test_seat/app/package.json b/003_test/002_mobile/01_test_seat/app/package.json new file mode 100644 index 0000000..ed00847 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/package.json @@ -0,0 +1,17 @@ +{ + "name": "app", + "version": "1.0.0", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@playwright/test": "^1.52.0", + "@types/node": "^22.15.18" + }, + "dependencies": { + "prettier": "^3.5.3" + } +} diff --git a/003_test/002_mobile/01_test_seat/app/parking/_GUIDELINE.md b/003_test/002_mobile/01_test_seat/app/parking/_GUIDELINE.md new file mode 100644 index 0000000..e69de29 diff --git a/003_test/002_mobile/01_test_seat/app/parking/example.spec.ts b/003_test/002_mobile/01_test_seat/app/parking/example.spec.ts new file mode 100644 index 0000000..54a906a --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/parking/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); diff --git a/003_test/002_mobile/01_test_seat/app/parking/helloworld.spec.ts.demo b/003_test/002_mobile/01_test_seat/app/parking/helloworld.spec.ts.demo new file mode 100644 index 0000000..e77d52a --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/parking/helloworld.spec.ts.demo @@ -0,0 +1,30 @@ +import { test, expect } from '@playwright/test'; +import { HELLO } from '../_config/helloworld'; + +test('fresh user should appears sign in page', async ({ page }) => { + await page.goto('http://192.168.222.199:3000/dashboard'); + + console.log({ HELLO }); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Sign in | Custom | Auth | demo cms/); +}); + +// test('fresh user login', async ({ page }) => { +// await page.goto('http://192.168.222.199:3000/dashboard'); + +// // Expect a title "to contain" a substring. +// const emailField = page.getByPlaceholder('e.g. admin@123.com'); + +// await emailField.press('Enter'); +// }); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); diff --git a/003_test/002_mobile/01_test_seat/app/playwright.config.ts b/003_test/002_mobile/01_test_seat/app/playwright.config.ts new file mode 100644 index 0000000..fe93cae --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + // { + // name: 'chromium', + // use: { ...devices['Desktop Chrome'] }, + // }, + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/003_test/002_mobile/01_test_seat/app/run.sh b/003_test/002_mobile/01_test_seat/app/run.sh new file mode 100755 index 0000000..d9e9233 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/run.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -ex + +# npx playwright test +npx playwright --version + +npx playwright test --ui + +npx playwright show-report --host 0.0.0.0 + +echo "done" diff --git a/003_test/002_mobile/01_test_seat/app/scripts/001_setup.sh b/003_test/002_mobile/01_test_seat/app/scripts/001_setup.sh new file mode 100755 index 0000000..3bd6987 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/scripts/001_setup.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -ex + +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash + +echo "done" diff --git a/003_test/002_mobile/01_test_seat/app/scripts/002_setup_playwright.sh b/003_test/002_mobile/01_test_seat/app/scripts/002_setup_playwright.sh new file mode 100755 index 0000000..e8cfc3f --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/scripts/002_setup_playwright.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -ex + +npx playwright install +npx playwright install-deps + +echo "done" diff --git a/003_test/002_mobile/01_test_seat/app/scripts/003_google_chrome.sh b/003_test/002_mobile/01_test_seat/app/scripts/003_google_chrome.sh new file mode 100755 index 0000000..b8dcfe7 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/scripts/003_google_chrome.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +echo "**** install chrome ****" +apt update +apt install -y wget +wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - +echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | tee /etc/apt/sources.list.d/google-chrome.list +apt update +apt install -y google-chrome-stable + +echo "install chrome done" diff --git a/003_test/002_mobile/01_test_seat/app/scripts/run_all.sh b/003_test/002_mobile/01_test_seat/app/scripts/run_all.sh new file mode 100755 index 0000000..a3e038e --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/scripts/run_all.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -ex + +cd scripts +./001_setup.sh 2>&1 | tee setup.log +./002_setup_playwright.sh 2>&1 | tee setup.log +sudo ./003_google_chrome.sh 2>&1 | tee setup.log + +cd .. + +echo "done" diff --git a/003_test/002_mobile/01_test_seat/app/tests-examples/demo-todo-app.spec.ts b/003_test/002_mobile/01_test_seat/app/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..c25eeb1 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,416 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = ['buy some cheese', 'feed the cat', 'book a doctors appointment'] as const; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([TODO_ITEMS[0]]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count'); + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect( + todoItem.locator('label', { + hasText: TODO_ITEMS[1], + }) + ).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], 'buy some sausages', TODO_ITEMS[2]]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count'); + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction((e) => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction((e) => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction((t) => { + return JSON.parse(localStorage['react-todos']) + .map((todo: any) => todo.title) + .includes(t); + }, title); +} diff --git a/003_test/002_mobile/01_test_seat/app/tests/REQ0020/001_fresh-user-should-appears-sign-in-page.spec.ts b/003_test/002_mobile/01_test_seat/app/tests/REQ0020/001_fresh-user-should-appears-sign-in-page.spec.ts new file mode 100644 index 0000000..7c605d2 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/tests/REQ0020/001_fresh-user-should-appears-sign-in-page.spec.ts @@ -0,0 +1,32 @@ +// tests/REQ0006/001_fresh-user-should-appears-sign-in-page.spec.ts +import { test, expect } from '@playwright/test'; +// +import { CMS_HOST, HELLO } from '../_config/helloworld'; +import { TS_HELLO } from '../_test_set/helloworld'; + +test('fresh user should appears sign in page', async ({ page }) => { + console.log({ HELLO, TS_HELLO }); + await page.goto(`${CMS_HOST}/dashboard`); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Sign in | Custom | Auth | demo cms/); +}); + +// test('fresh user login', async ({ page }) => { +// await page.goto('http://192.168.222.199:3000/dashboard'); + +// // Expect a title "to contain" a substring. +// const emailField = page.getByPlaceholder('e.g. admin@123.com'); + +// await emailField.press('Enter'); +// }); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); diff --git a/003_test/002_mobile/01_test_seat/app/tests/REQ0020/002_user-login.spec.ts b/003_test/002_mobile/01_test_seat/app/tests/REQ0020/002_user-login.spec.ts new file mode 100644 index 0000000..6e3f699 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/tests/REQ0020/002_user-login.spec.ts @@ -0,0 +1,40 @@ +// tests/REQ0006/001_fresh-user-should-appears-sign-in-page.spec.ts +import { test, expect } from '@playwright/test'; +// +import { CMS_HOST, HELLO } from '../_config/helloworld'; +import { TS_HELLO } from '../_test_set/helloworld'; + +test('user login', async ({ page }) => { + console.log({ HELLO, TS_HELLO }); + + await page.goto(`${CMS_HOST}/dashboard`); + await expect(page).toHaveTitle(/Sign in | Custom | Auth | demo cms/); + + await page.getByPlaceholder('name@example.com').pressSequentially('user5@123.com'); + await page.getByPlaceholder('password').pressSequentially('user5@123.com'); + await page.waitForTimeout(1 * 1000); + + await page.getByRole('button', { name: 'Sign in' }).click(); + await page.waitForTimeout(1 * 1000); + + await expect(page).toHaveTitle(/192.168.222.199:3000\/dashboard/); +}); + +// test('fresh user login', async ({ page }) => { +// await page.goto('http://192.168.222.199:3000/dashboard'); + +// // Expect a title "to contain" a substring. +// const emailField = page.getByPlaceholder('e.g. admin@123.com'); + +// await emailField.press('Enter'); +// }); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); diff --git a/003_test/002_mobile/01_test_seat/app/tests/_config/helloworld.ts b/003_test/002_mobile/01_test_seat/app/tests/_config/helloworld.ts new file mode 100644 index 0000000..cfa3625 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/tests/_config/helloworld.ts @@ -0,0 +1,2 @@ +export const HELLO = 'WORLD'; +export const CMS_HOST = 'http://192.168.222.199:3000'; diff --git a/003_test/002_mobile/01_test_seat/app/tests/_test_set/helloworld.ts b/003_test/002_mobile/01_test_seat/app/tests/_test_set/helloworld.ts new file mode 100644 index 0000000..17efef4 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/app/tests/_test_set/helloworld.ts @@ -0,0 +1 @@ +export const TS_HELLO = 'WORLD'; diff --git a/003_test/002_mobile/01_test_seat/dc_up.sh b/003_test/002_mobile/01_test_seat/dc_up.sh new file mode 100755 index 0000000..0f56a7c --- /dev/null +++ b/003_test/002_mobile/01_test_seat/dc_up.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -ex + +# docker compose build | tee dc_build.log + +# docker compose kill +# docker compose down +docker compose up -d + +echo "done" diff --git a/003_test/002_mobile/01_test_seat/docker-compose.yml b/003_test/002_mobile/01_test_seat/docker-compose.yml new file mode 100644 index 0000000..d817fb3 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/docker-compose.yml @@ -0,0 +1,32 @@ +name: 002_mobile + +services: + chromium: + build: . + security_opt: + - seccomp:unconfined #optional + environment: + - TITLE=mobile-test-seat-1 + - FM_HOME=/app + - PUID=1000 # default 911 + - PGID=1000 # default 911 + - TZ=Etc/Asia_HongKong + - CHROME_CLI=https://www.gmail.com/ #optional + # - CUSTOM_PORT=3000 # Internal port the container listens on for http if it needs to be swapped from the default 3000. + - CUSTOM_HTTPS_PORT=3001 # Internal port the container listens on for https if it needs to be swapped from the default 3001. + - NO_FULL=1 # Do n/ot autmatically fullscreen applications when using openbox. + - LC_ALL=en_US.UTF-8 # Set the locale. + - INSTALL_PACKAGES=fonts-noto-cjk + ports: + # - 6000:3000 + - 6002:3001 # https vnc ip + - 9324:9323 # report ip + volumes: + - ./app:/app + shm_size: '1gb' + deploy: + resources: + limits: + cpus: 1 + reservations: + cpus: 0.01 diff --git a/003_test/002_mobile/01_test_seat/dockerfile b/003_test/002_mobile/01_test_seat/dockerfile new file mode 100644 index 0000000..03cd69d --- /dev/null +++ b/003_test/002_mobile/01_test_seat/dockerfile @@ -0,0 +1,27 @@ +FROM ghcr.io/linuxserver/baseimage-kasmvnc:ubuntujammy + +# set version label +ARG BUILD_DATE +ARG VERSION +LABEL build_version="Linuxserver.io version:- ${VERSION} Build-date:- ${BUILD_DATE}" +LABEL maintainer="thelamer" +ARG DEBIAN_FRONTEND="noninteractive" + +# title +ENV TITLE=test-seat + +RUN rm /bin/sh && ln -s /bin/bash /bin/sh + +RUN apt-get update +RUN apt-get install -qqy wget git curl + +# Set up the working directory +WORKDIR /app + +# ports and volumes +EXPOSE 3000 + +VOLUME /config + +COPY ./setup/ /setup +RUN chmod +x /setup/*.sh diff --git a/003_test/002_mobile/01_test_seat/setup/001_nvm.sh b/003_test/002_mobile/01_test_seat/setup/001_nvm.sh new file mode 100644 index 0000000..cd4ef2b --- /dev/null +++ b/003_test/002_mobile/01_test_seat/setup/001_nvm.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -e + +echo "**** install nvm ****" + +# Download and install Node.js (you may need to restart the terminal) +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash + +source ~/.nvm/nvm.sh + +nvm install 20 + +nvm alias default 20 + +nvm use default + +npm install -g yarn + +echo "source ~/.nvm/nvm.sh" >>~/.bashrc + +echo "install nvm done" diff --git a/003_test/002_mobile/01_test_seat/setup/run_all.sh b/003_test/002_mobile/01_test_seat/setup/run_all.sh new file mode 100644 index 0000000..0f90368 --- /dev/null +++ b/003_test/002_mobile/01_test_seat/setup/run_all.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -ex + +# ls -1 /setup/0*.sh | while read script; do +# echo "Running $script" +# bash "$script" & +# done + +/setup/001_nvm.sh & +sudo /setup/002_google_chrome.sh & + +wait + +echo "done" diff --git a/003_test/002_mobile/_GUIDELINES.md b/003_test/002_mobile/_GUIDELINES.md new file mode 100644 index 0000000..949ec20 --- /dev/null +++ b/003_test/002_mobile/_GUIDELINES.md @@ -0,0 +1,7 @@ +# GUIDELINES + +this folder contains test and validation of the project + +## highlight + +- `01_test_seat` testing for mobile diff --git a/003_test/_GUIDELINES.md b/003_test/_GUIDELINES.md new file mode 100644 index 0000000..909b9b7 --- /dev/null +++ b/003_test/_GUIDELINES.md @@ -0,0 +1,9 @@ +# GUIDELINES + +this folder contains test and validation of the project + +## highlight + +- `001_desktop` testing for desktop +- `002_mobile` testing for mobile +- `003_e2e` testing for end-to-end diff --git a/003_test/default.code-workspace b/003_test/default.code-workspace new file mode 100644 index 0000000..16f0050 --- /dev/null +++ b/003_test/default.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../001_documentation" + }, + { + "path": "../000_AI_WORKSPACE" + } + ], + "settings": {} +} diff --git a/003_test/desktop-test.gif b/003_test/desktop-test.gif new file mode 100644 index 0000000..1fb2813 Binary files /dev/null and b/003_test/desktop-test.gif differ diff --git a/003_test/mobile-test.gif b/003_test/mobile-test.gif new file mode 100644 index 0000000..b1beddc Binary files /dev/null and b/003_test/mobile-test.gif differ diff --git a/003_test/scripts/dc_dev.sh b/003_test/scripts/dc_dev.sh new file mode 100644 index 0000000..c1dae43 --- /dev/null +++ b/003_test/scripts/dc_dev.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -x + +echo "done"