diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..626736c --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,47 @@ +name: Generate lighthouse test report on PR +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + automation-test: + runs-on: ubuntu-latest + env: + COOKIE_KEY: '1267b291500365c42043e04bc69cf24a31495bd8936fc8d6794283675e288fad755971922d45cf1ca0b438df4fc847f39cb0b2aceb3a45673eff231cddb88dc9' + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm install + + - name: Install Lighthouse + run: npm install -g @lhci/cli@0.13.x + + - name: Run Lighthouse + # run: lhci autorun --upload.githubToken="$LHCI_GITHUB_TOKEN" || echo "LHCI failed!" + run: lhci autorun + # env: + # LHCI_GITHUB_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} + + - name: install playwright + run: npx playwright install + + - name: run playwright + id: tests + # run: TESTPARAM=${{secrets.MAIL_PASSWORD}} npx playwright test --project=firefox + run: npx playwright test --project=firefox + + # - name: Check test result and comment + # uses: peter-evans/create-or-update-comment@v1 + # with: + # token: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} + # issue-number: ${{ github.event.pull_request.number }} + # body: | + # ${{ steps.tests.outcome == '2 passed' && 'Test passed :heavy_check_mark:' || 'Test failed, Please check logs for details.' }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..203df33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/.lighthouseci \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/lighthouserc.js b/lighthouserc.js new file mode 100644 index 0000000..2082feb --- /dev/null +++ b/lighthouserc.js @@ -0,0 +1,16 @@ +module.exports = { + ci: { + collect: { + url: ['https://yral.com/'], + }, + upload: { + target: 'temporary-public-storage', + }, + assert: { + assertions: { + "categories:performance": ["warn", {"minScore": 0.3}], + "categories:accessibility": ["warn", {"minScore": 0.5}] + }, + }, + }, +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b0a8dfa --- /dev/null +++ b/package-lock.json @@ -0,0 +1,87 @@ +{ + "name": "hot-or-not-web-leptos-ssr-e2e-test", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "playwright": "^1.43.1" + }, + "devDependencies": { + "@playwright/test": "^1.43.1", + "@types/node": "^20.12.7" + } + }, + "node_modules/@playwright/test": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", + "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", + "dev": true, + "dependencies": { + "playwright": "1.43.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz", + "integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==", + "dependencies": { + "playwright-core": "1.43.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz", + "integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..819643c --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "playwright": "^1.43.1" + }, + "devDependencies": { + "@playwright/test": "^1.43.1", + "@types/node": "^20.12.7" + }, + "scripts": {} +} diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..b15af2e --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,79 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = 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/tests-examples/demo-todo-app.spec.js b/tests-examples/demo-todo-app.spec.js new file mode 100644 index 0000000..e2eb87c --- /dev/null +++ b/tests-examples/demo-todo-app.spec.js @@ -0,0 +1,449 @@ +// @ts-check +const { test, expect } = require('@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' +]; + +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) { + // 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'); + } +} + +/** + * @param {import('@playwright/test').Page} page + * @param {number} expected + */ + async function checkNumberOfTodosInLocalStorage(page, expected) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {number} expected + */ + async function checkNumberOfCompletedTodosInLocalStorage(page, expected) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter(i => i.completed).length === e; + }, expected); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {string} title + */ +async function checkTodosInLocalStorage(page, title) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map(i => i.title).includes(t); + }, title); +} diff --git a/tests/example.spec.js b/tests/example.spec.js new file mode 100644 index 0000000..cd4f49a --- /dev/null +++ b/tests/example.spec.js @@ -0,0 +1,180 @@ +import { test, expect } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://yral.com/'); +}); + +test.describe('New test', () => { + test('Has title', async ({page}) => { + + try { // Check opening status + const title = await page.title(); + console.log(title); // Log the page title + // expect(title).toContain('Expected Title'); // Replace with expected title + expect(title).toBe("Yral"); + } catch (error) { + console.error("error checking title",error); // Handle errors gracefully + } + + }); + + test('Test video playing', async ({page}) => { + + //try with 'video' element + let video = page.locator('video').nth(0); + await page.waitForTimeout(3000); + + await expect(video).toBeVisible(); + console.log("is visible"); + + await expect(video).not.toHaveAttribute('paused'); + console.log("is not paused"); + + await expect(video).toHaveAttribute('muted'); + console.log("is muted"); + + // click unmute button + await page.locator('.fixed').first().click(); + await page.waitForTimeout(3000); + // // css selector of unmute button + // const buttonLocator = page.locator('body > main > div.h-full.w-full.overflow-hidden.overflow-y-auto > div > button > svg > path').click(); + + let video_new = page.locator('video').nth(0); + // await expect(video_new).not.toHaveAttribute('muted'); + // console.log("is not muted"); + + const duration = await video.evaluate(videoEle => videoEle.duration); + console.log('Video Duration:', duration); + + // const isVideoPlaying = await video.evaluate(() => document.querySelector('video').autoplay); // Check playback state + // console.log("is video playing", isVideoPlaying); + + //check if video is playing after 5 seconds + await page.waitForTimeout(5000); + try { + await expect(video).not.toHaveAttribute('paused'); + console.log("after 5 seconds, not paused"); + } catch (error) { + console.error("video paused after 5 seconds",error); // Handle errors gracefully + } + + //scroll to new video based on its locator + let new_video = page.locator('video').nth(2); + await new_video.scrollIntoViewIfNeeded(); + console.log("scroll to new video"); + + await expect(new_video).toBeVisible(); + console.log("2nd video is visible"); + + await expect(new_video).not.toHaveAttribute('paused'); + console.log("2nd video is not paused"); + + const new_duration = await new_video.evaluate(new_video => new_video.duration); + console.log('2nd Video Duration:', new_duration); + + await page.waitForTimeout(3000); + + //scroll to new video based on its locator + let third_video = page.locator('video').nth(5); + await third_video.scrollIntoViewIfNeeded(); + console.log("scroll to third video"); + + await expect(third_video).toBeVisible(); + console.log("3rd video is visible"); + + await expect(third_video).not.toHaveAttribute('paused'); + console.log("3rd video is not paused"); + + const third_duration = await third_video.evaluate(new_video => new_video.duration); + console.log('3rd Video Duration:', third_duration); + + // await page.waitForTimeout(3000); + + }); + + // test('Test google login', async ({page}) => { + + // // login from wallet: using headfull mode + // try { + // console.log('Log in test'); + + // // const environment_pass = global.expect; + // const environment_pass = process.env.TESTPARAM; + + // await page.waitForTimeout(3000); + + // await page.getByRole('navigation').getByRole('link').nth(3).click(); + // await page.getByRole('button', { name: 'Login to claim your COYNs' }).click(); + // const page1Promise = page.waitForEvent('popup'); + // await page.getByRole('button', { name: 'Google Sign-In' }).click(); + // const page1 = await page1Promise; + // await page1.getByLabel('Email or phone').click(); + // await page1.getByLabel('Email or phone').fill('testautomationyral@gmail.com'); + + // await page.waitForTimeout(2000); + // await page1.getByLabel('Email or phone').press('Enter'); + + // await page1.getByLabel('Enter your password').click(); + // await page1.getByLabel('Enter your password').fill(environment_pass); + // await page.waitForTimeout(2000); + + // await page1.getByLabel('Enter your password').press('Enter'); + // // await page1.pause(); + // await page.waitForTimeout(3000); + + // console.log('Logged in succesfully'); + // // await page.getByRole('navigation').getByRole('link').first().click(); + + // await page.waitForTimeout(3000); + // //request to the API endpoint to fetch the response + // const response = await fetch('https://yral-metadata.fly.dev/metadata/gzlng-jqzta-5kubz-4nyam-5so2e-tsoio-ijv2s-47dsw-7ksd7-pe3eb-zqe'); + // await page.waitForTimeout(2000); + + // console.log(response.status); + // const responseData = await response.json(); + // console.log(responseData); + + // } catch (error) { + // console.error("error in login"); + // } + // }); + + // test('TEST UPLOAD FLOW', async ({page}) => { + + // console.log('Uploading test'); + + // await page.waitForTimeout(2000); + + // await page.getByRole('navigation').getByRole('link').nth(2).click(); + + // await page.setInputFiles('#dropzone-file', './test1.mp4'); + + // // TODO + // // //endpoint to fetch the response + // // const response = await fetch('https://yral.com/api/stream_to_offchain_agent11958048345285885967'); + // // console.log(response); + // // const responseData = await response.json(); + + // // console.log(responseData); + // // // Perform assertions based on the API response + // // expect(responseData.status).toBe(200); + + // await page.getByPlaceholder('Write your description here..').click(); + // await page.getByPlaceholder('Write your description here..').fill('test video upload'); + // await page.getByPlaceholder('#hashtag1,#hashtag2,#hashtag3').click(); + // await page.getByPlaceholder('#hashtag1,#hashtag2,#hashtag3').fill('#try'); + + // // const button = + // // await button.click('button[data-api-trigger="true"]'); + + // await page.getByRole('button', { name: 'Upload Video' }).click(); + // await page.pause(); + + // // await button.click('button[data-api-trigger="true"]'); + + // await page.getByRole('navigation').getByRole('link').first().click(); + // await page.waitForTimeout(5000); + + // }); + +});