From 57d41f2ac5bf06f0eaa577161c5c72ab2ac6a0bc Mon Sep 17 00:00:00 2001 From: kientrinh-dev Date: Mon, 5 Jan 2026 20:55:30 +0700 Subject: [PATCH 1/2] Init Agent Feature Automation --- .../agents/playwright-test-generator.agent.md | 118 ++ .../agents/playwright-test-healer.agent.md | 65 + .../agents/playwright-test-planner.agent.md | 90 + .github/workflows/copilot-setup-steps.yml | 34 + package-lock.json | 1521 ++++++++++++++++- package.json | 2 +- specs/PROJECT_CONTEXT.md | 261 +++ src/tests/web/seed.spec.ts | 23 + yarn.lock | 576 +++---- 9 files changed, 2291 insertions(+), 399 deletions(-) create mode 100644 .github/agents/playwright-test-generator.agent.md create mode 100644 .github/agents/playwright-test-healer.agent.md create mode 100644 .github/agents/playwright-test-planner.agent.md create mode 100644 .github/workflows/copilot-setup-steps.yml create mode 100644 specs/PROJECT_CONTEXT.md create mode 100644 src/tests/web/seed.spec.ts diff --git a/.github/agents/playwright-test-generator.agent.md b/.github/agents/playwright-test-generator.agent.md new file mode 100644 index 00000000..6fbc19cf --- /dev/null +++ b/.github/agents/playwright-test-generator.agent.md @@ -0,0 +1,118 @@ +--- +name: playwright-test-generator +description: Use this agent to create Playwright tests from test plans +tools: + - search + - playwright-test/browser_click + - playwright-test/browser_drag + - playwright-test/browser_evaluate + - playwright-test/browser_file_upload + - playwright-test/browser_handle_dialog + - playwright-test/browser_hover + - playwright-test/browser_navigate + - playwright-test/browser_press_key + - playwright-test/browser_select_option + - playwright-test/browser_snapshot + - playwright-test/browser_type + - playwright-test/browser_verify_element_visible + - playwright-test/browser_verify_list_visible + - playwright-test/browser_verify_text_visible + - playwright-test/browser_verify_value + - playwright-test/browser_wait_for + - playwright-test/generator_read_log + - playwright-test/generator_setup_page + - playwright-test/generator_write_test + +model: Claude Sonnet 4 +mcp-servers: + playwright-test: + type: stdio + command: npx + args: + - playwright + - run-test-mcp-server + tools: + - '*' +--- + +You are a Playwright Test Generator. Before generating, **read `specs/PROJECT_CONTEXT.md`** first. + +## Selector Priority (MANDATORY) + +1. **Selector classes** from `src/data/selectors/` (MessageSelector, etc.) +2. **`generateE2eSelector()`** with valid keys +3. **`getByRole()`/`getByText()`** only if no data-e2e exists +4. **NEVER** use fragile CSS/Tailwind selectors + +## Workflow + +1. Read test plan and `specs/PROJECT_CONTEXT.md` +2. Run `generator_setup_page` +3. Execute each step with browser tools +4. Run `generator_read_log` +5. Run `generator_write_test` with generated code + +## Auth Setup Pattern + +```typescript +import { test, expect } from '@playwright/test'; +import { AuthHelper } from '@/utils/authHelper'; +import { AccountCredentials, GLOBAL_CONFIG } from '@/config/environment'; +import { ROUTES } from '@/selectors'; +import joinUrlPaths from '@/utils/joinUrlPaths'; + +test.describe('Feature', () => { + test.beforeEach(async ({ page }) => { + const credentials = await AuthHelper.setupAuthWithEmailPassword( + page, + AccountCredentials.account2 + ); + await AuthHelper.prepareBeforeTest( + page, + joinUrlPaths(GLOBAL_CONFIG.LOCAL_BASE_URL, ROUTES.DIRECT_FRIENDS), + credentials + ); + }); + + test('Test Name', async ({ page }) => { + // Implementation + }); +}); +``` + +## Code Examples + +**Using Selector Class:** + +```typescript +import MessageSelector from '@/data/selectors/MessageSelector'; + +const selector = new MessageSelector(page); +await selector.messageInput.fill(`Test ${Date.now()}`); +await selector.messageInput.press('Enter'); +``` + +**Using Page Object:** + +```typescript +import { MessagePage } from '@/pages/MessagePage'; + +const messagePage = new MessagePage(page); +await messagePage.sendMessageWhenInDM('Hello'); +``` + +**Using generateE2eSelector:** + +```typescript +import { generateE2eSelector } from '@/utils/generateE2eSelector'; + +await page.locator(generateE2eSelector('chat.direct_message.button.button_plus')).click(); +``` + +## Rules + +- One test per file +- File location: `src/tests/web//.spec.ts` +- Use `Date.now()` for unique test data +- Add step comments before actions +- Use locator waits, avoid `waitForTimeout()` diff --git a/.github/agents/playwright-test-healer.agent.md b/.github/agents/playwright-test-healer.agent.md new file mode 100644 index 00000000..02f713df --- /dev/null +++ b/.github/agents/playwright-test-healer.agent.md @@ -0,0 +1,65 @@ +--- +name: playwright-test-healer +description: Use this agent when you need to debug and fix failing Playwright tests +tools: + - search + - edit + - playwright-test/browser_console_messages + - playwright-test/browser_evaluate + - playwright-test/browser_generate_locator + - playwright-test/browser_network_requests + - playwright-test/browser_snapshot + - playwright-test/test_debug + - playwright-test/test_list + - playwright-test/test_run +model: Claude Sonnet 4 +mcp-servers: + playwright-test: + type: stdio + command: npx + args: + - playwright + - run-test-mcp-server + tools: + - '*' +--- + +You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and +resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix +broken Playwright tests using a methodical approach. + +Your workflow: + +1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests +2. **Debug failed tests**: For each failing test run `test_debug`. +3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to: + - Examine the error details + - Capture page snapshot to understand the context + - Analyze selectors, timing issues, or assertion failures +4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining: + - Element selectors that may have changed + - Timing and synchronization issues + - Data dependencies or test environment problems + - Application changes that broke test assumptions +5. **Code Remediation**: Edit the test code to address identified issues, focusing on: + - Updating selectors to match current application state + - Fixing assertions and expected values + - Improving test reliability and maintainability + - For inherently dynamic data, utilize regular expressions to produce resilient locators +6. **Verification**: Restart the test after each fix to validate the changes +7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly + +Key principles: + +- Be systematic and thorough in your debugging approach +- Document your findings and reasoning for each fix +- Prefer robust, maintainable solutions over quick hacks +- Use Playwright best practices for reliable test automation +- If multiple errors exist, fix them one at a time and retest +- Provide clear explanations of what was broken and how you fixed it +- You will continue this process until the test runs successfully without any failures or errors. +- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme() + so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead + of the expected behavior. +- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test. +- Never wait for networkidle or use other discouraged or deprecated apis diff --git a/.github/agents/playwright-test-planner.agent.md b/.github/agents/playwright-test-planner.agent.md new file mode 100644 index 00000000..acad2388 --- /dev/null +++ b/.github/agents/playwright-test-planner.agent.md @@ -0,0 +1,90 @@ +--- +name: playwright-test-planner +description: Use this agent to create comprehensive test plans for web applications +tools: + - search + - playwright-test/browser_click + - playwright-test/browser_close + - playwright-test/browser_console_messages + - playwright-test/browser_drag + - playwright-test/browser_evaluate + - playwright-test/browser_file_upload + - playwright-test/browser_handle_dialog + - playwright-test/browser_hover + - playwright-test/browser_navigate + - playwright-test/browser_navigate_back + - playwright-test/browser_network_requests + - playwright-test/browser_press_key + - playwright-test/browser_select_option + - playwright-test/browser_snapshot + - playwright-test/browser_take_screenshot + - playwright-test/browser_type + - playwright-test/browser_wait_for + - playwright-test/planner_setup_page + - playwright-test/planner_save_plan +model: Claude Sonnet 4 +mcp-servers: + playwright-test: + type: stdio + command: npx + args: + - playwright + - run-test-mcp-server + tools: + - '*' +--- + +You are an expert web test planner. Before creating any plan, **read `specs/PROJECT_CONTEXT.md`** first. + +## Path Conventions (MANDATORY) + +- Plans: `specs/test-plans/.plan.md` +- Tests: `src/tests/web//.spec.ts` +- Seed: `src/tests/web/seed.spec.ts` + +## Workflow + +1. Run `planner_setup_page` once to set up +2. Explore with `browser_*` tools (avoid screenshots) +3. Design scenarios: happy path, edge cases, error handling +4. Save plan with `planner_save_plan` + +## Plan Template + +```markdown +# Feature - Test Plan + +## Overview + +[Brief description] + +## Scenarios + +### Scenario Name + +**Seed:** `src/tests/web/seed.spec.ts` +**File:** `src/tests/web/Feature/scenario-name.spec.ts` + +**Implementation hints:** + +- Use `MessageSelector` / `MessagePage` / `MessageTestHelpers` +- Use `generateE2eSelector()` for locators +- Auth: `AuthHelper.setupAuthWithEmailPassword()` + `prepareBeforeTest()` + +**Steps:** + +1. [Action] +2. [Action] + +**Expected Results:** + +- [Assertable outcome] +``` + +## Available Resources + +**Selectors:** MessageSelector, HomePageSelector, FriendSelector, ClanSelector, ProfileSelector + +**Page Objects:** MessagePage, HomePage, ProfilePage, ClanPage + +**Helpers:** AuthHelper, MessageTestHelpers, DirectMessageHelper diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000..da3892d3 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,34 @@ +name: 'Copilot Setup Steps' + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Install dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + # Customize this step as needed + - name: Build application + run: npx run build diff --git a/package-lock.json b/package-lock.json index 1c99fd73..9cbb01d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,12 @@ "@faker-js/faker": "^9.0.0", "archiver": "^7.0.1", "aws-sdk": "^2.1692.0", + "crypto": "^1.0.1", "dotenv": "^16.4.5" }, "devDependencies": { "@estruyf/github-actions-reporter": "^1.10.0", - "@playwright/test": "^1.55.0", + "@playwright/test": "^1.57.0", "@types/archiver": "^6.0.3", "@types/node": "^22.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", @@ -25,10 +26,15 @@ "allure-js-commons": "^3.3.3", "allure-playwright": "^3.3.3", "eslint": "^9.0.0", + "eslint-formatter-sonarqube": "1.0.0", + "eslint-plugin-sonarjs": "^1.0.0", "husky": "^9.1.7", + "lint-staged": "^15.2.10", + "mezon-js": "^2.13.16", "nodemon": "^3.1.10", "playwright-bdd": "^8.3.1", "prettier": "^3.3.0", + "stop-only": "^3.1.2", "typescript": "^5.5.0" } }, @@ -138,16 +144,6 @@ "uuid": "10.0.0" } }, - "node_modules/@cucumber/gherkin-utils/node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@cucumber/gherkin-utils/node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -222,6 +218,448 @@ "dev": true, "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -606,13 +1044,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", - "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.55.0" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -621,6 +1059,14 @@ "node": ">=18" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@teppeis/multimaps": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-3.0.0.tgz", @@ -1007,6 +1453,22 @@ "@playwright/test": ">=1.36.0" } }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -1186,6 +1648,16 @@ "license": "Apache-2.0", "optional": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1409,6 +1881,22 @@ "dev": true, "license": "MIT" }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", @@ -1425,6 +1913,48 @@ "@colors/colors": "1.5.0" } }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1443,6 +1973,23 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/compress-commons": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", @@ -1520,10 +2067,17 @@ "node": "*" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", + "license": "ISC" + }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1600,6 +2154,16 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -1610,6 +2174,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1640,6 +2217,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1714,6 +2333,26 @@ } } }, + "node_modules/eslint-formatter-sonarqube": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-sonarqube/-/eslint-formatter-sonarqube-1.0.0.tgz", + "integrity": "sha512-6cTJeUfQm7F5saT6y1jvFNNYPPJyQd1UJOYbPTCSz66gBanPKc0BSANt5m/pK7UIRtjhCatK6L8tIfJ6SnhNWw==", + "dev": true, + "license": "ISC" + }, + "node_modules/eslint-plugin-sonarjs": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-1.0.4.tgz", + "integrity": "sha512-jF0eGCUsq/HzMub4ExAyD8x1oEgjOyB9XVytYGyWgSFvdiJQJp6IuP7RmtauCf06o6N/kZErh+zW4b10y1WZ+Q==", + "dev": true, + "license": "LGPL-3.0-only", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -1877,6 +2516,13 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -1886,6 +2532,43 @@ "node": ">=0.4.x" } }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2068,7 +2751,20 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-intrinsic": { @@ -2108,6 +2804,19 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -2240,6 +2949,16 @@ "node": ">= 0.4" } }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -2500,6 +3219,13 @@ "node": ">= 0.6.0" } }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2600,6 +3326,134 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lint-staged": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2636,6 +3490,115 @@ "dev": true, "license": "MIT" }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2684,6 +3647,13 @@ "is-buffer": "~1.1.6" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2694,6 +3664,20 @@ "node": ">= 8" } }, + "node_modules/mezon-js": { + "version": "2.13.79", + "resolved": "https://registry.npmjs.org/mezon-js/-/mezon-js-2.13.79.tgz", + "integrity": "sha512-ilDzKDwT7nCAY6PA8qyehcIVnMXHSa665l9IHdbRSghRdd0kishsNCswAOK+vYCegJvNfyEWOmml8kLU6r53bg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "^1.1.1", + "base64-arraybuffer": "^1.0.2", + "esbuild": "^0.25.5", + "js-base64": "^3.7.4", + "whatwg-fetch": "^3.6.2" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2708,6 +3692,32 @@ "node": ">=8.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -2723,6 +3733,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -2746,6 +3766,13 @@ "dev": true, "license": "MIT" }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", @@ -2771,41 +3798,96 @@ "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "wrappy": "1" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/optionator": { @@ -2826,6 +3908,16 @@ "node": ">= 0.8.0" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2925,14 +4017,27 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/playwright": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", - "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.55.0" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -2977,16 +4082,6 @@ "@playwright/test": ">=1.42" } }, - "node_modules/playwright-bdd/node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/playwright-bdd/node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -3011,9 +4106,9 @@ } }, "node_modules/playwright-core": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", - "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3080,6 +4175,17 @@ "dev": true, "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3260,6 +4366,39 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3271,6 +4410,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3414,6 +4560,49 @@ "node": ">=10" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3435,6 +4624,159 @@ "source-map": "^0.6.0" } }, + "node_modules/stop-only": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/stop-only/-/stop-only-3.4.4.tgz", + "integrity": "sha512-fWpzRixI9sM8fnldjMMGha0ckjUHghedXB+WWaEhFypdeRPiA3rbM8tlyKzb/hb7S672AO78I7/fvlSRQ0fAPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4.4.3", + "execa": "0.11.0", + "minimist": "1.2.8" + }, + "bin": { + "stop-only": "bin/stop-only.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stop-only/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/stop-only/node_modules/execa": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.11.0.tgz", + "integrity": "sha512-k5AR22vCt1DcfeiRixW46U5tMLtBg44ssdJM9PiXw3D8Bn5qyxFCSnKY/eR22y+ctFDGPqafpaXg2G4Emyua4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stop-only/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stop-only/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-only/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stop-only/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/stop-only/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/stop-only/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-only/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-only/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/stop-only/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/streamx": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", @@ -3457,6 +4799,16 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3565,6 +4917,29 @@ "node": ">=8" } }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3778,6 +5153,13 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3915,6 +5297,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/xml2js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", @@ -3947,6 +5336,22 @@ "node": ">=8.0" } }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 45ca330b..d90d204f 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "license": "MIT", "devDependencies": { "@estruyf/github-actions-reporter": "^1.10.0", - "@playwright/test": "^1.55.0", + "@playwright/test": "^1.57.0", "@types/archiver": "^6.0.3", "@types/node": "^22.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", diff --git a/specs/PROJECT_CONTEXT.md b/specs/PROJECT_CONTEXT.md new file mode 100644 index 00000000..668fc0a3 --- /dev/null +++ b/specs/PROJECT_CONTEXT.md @@ -0,0 +1,261 @@ +# Project Context (Mezon E2E) — Playwright Test Agents + +This file is the single source of truth for **Planner** and **Generator** to understand this repo’s structure, conventions, and how to work with **`data-e2e`** selectors. + +Goal: the plan describes **user flows** + **assertable expectations**; the generator produces code that follows **this repo’s patterns** (data-e2e + selector classes + POM + helpers), avoiding fragile `getByText()`/random CSS. + +--- + +## 1) Paths & repository structure (IMPORTANT) + +- **Web tests**: `src/tests/web/**` +- **Seed test**: `src/tests/web/seed.spec.ts` +- **Test plans (Markdown)**: `specs/test-plans/**` (Planner should save here) +- **Page Objects**: `src/pages/**` +- **Selector classes**: `src/data/selectors/**` +- **Helpers/Utilities**: `src/utils/**` +- **E2E selector keys & ROUTES**: `src/selectors/index.ts` + +### Plan file-path rules + +- If a scenario declares a file path, it **MUST** be: + - `src/tests/web//.spec.ts` +- Do **NOT** use `tests/...` (missing `src/`) because Playwright is configured around `./src/tests`. + +--- + +## 2) Environment & Base URL (IMPORTANT) + +This repo loads env via `dotenv` and reads `process.env.BASE_URL`. + +- `GLOBAL_CONFIG.LOCAL_BASE_URL = process.env.BASE_URL || ''` +- If `BASE_URL` is empty, seed/auth helpers may navigate to an invalid URL. + +### Minimal requirement for stable runs/generation + +Set `BASE_URL` via `.env` (recommended) or shell: + +```bash +BASE_URL=https://dev-mezon.nccsoft.vn +``` + +--- + +## 3) Selector strategy — prefer `data-e2e` (MANDATORY) + +### 3.1 Why Planner “doesn’t know” the `data-e2e` keys + +Planner typically writes behavior-level steps (“click login”, “type message”) and may not know the exact `data-e2e` keys. + +Correct approach for this repo: + +- Plan: steps + expected results (what, not how). +- Generator: executes steps and uses **selector classes** and **`generateE2eSelector()`** to target `data-e2e`. + +### 3.2 Mandatory rules + +- **Priority 1**: use **Selector classes** (`src/data/selectors/*Selector.ts`) +- **Priority 2**: use `generateE2eSelector()` with a valid `E2eKeyType` +- **Only if needed**: `page.getByText()` / `page.getByRole()` (when no `data-e2e` exists) +- **Avoid**: fragile CSS selectors based on dynamic/tailwind classes + +### 3.3 `generateE2eSelector()` (repo standard) + +```ts +import { generateE2eSelector } from '@/utils/generateE2eSelector'; + +const dmPlusBtn = page.locator(generateE2eSelector('chat.direct_message.button.button_plus')); +``` + +Valid keys come from: + +- `src/selectors/index.ts` (`E2eKeyType`) +- `DATA_E2E_IDENTIFIER` dot-keys (same file) + +--- + +## 4) Selector classes (PREFERRED) + +Selectors are already packaged per screen/feature. Generator should prefer: + +- `HomePageSelector` +- `MessageSelector` +- `FriendSelector` +- `ClanSelector` +- `ProfileSelector` +- `CategorySettingSelector` +- `ChannelSettingSelector` +- `ClanSettingSelector` + +Example (Messages): + +```ts +import MessageSelector from '@/data/selectors/MessageSelector'; + +const sel = new MessageSelector(page); +await sel.buttonCreateGroupSidebar.click(); +await sel.messageInput.fill('hello'); +await sel.messageInput.press('Enter'); +``` + +--- + +## 5) Page Objects & Helpers (PREFERRED) + +### 5.1 Page Objects (POM) + +Use page objects from `src/pages/` whenever possible: + +- `HomePage` +- `LoginPage` +- `MessagePage` +- `ClanPage`, etc. + +Example: + +```ts +import { MessagePage } from '@/pages/MessagePage'; + +const messagePage = new MessagePage(page); +await messagePage.sendMessageWhenInDM('Hello'); +``` + +### 5.2 Helpers + +Use helpers to standardize flows: + +- **Auth**: `AuthHelper` (`src/utils/authHelper.ts`) +- **Messages**: `MessageTestHelpers` (`src/utils/messageHelpers.ts`) +- **Direct message**: `DirectMessageHelper` (`src/utils/directMessageHelper.ts`) + +Example: + +```ts +import { MessageTestHelpers } from '@/utils/messageHelpers'; + +const helper = new MessageTestHelpers(page); +await helper.sendTextMessage(`Test message ${Date.now()}`); +``` + +--- + +## 6) Test setup pattern (MANDATORY for auth-required tests) + +Repo convention: + +- `AuthHelper.setupAuthWithEmailPassword(...)` +- `AuthHelper.prepareBeforeTest(page, url, credentials)` +- navigation via `joinUrlPaths(GLOBAL_CONFIG.LOCAL_BASE_URL, ROUTES.XXX)` + +Standard skeleton: + +```ts +import { test, expect } from '@playwright/test'; +import { AuthHelper } from '@/utils/authHelper'; +import { AccountCredentials, GLOBAL_CONFIG } from '@/config/environment'; +import { ROUTES } from '@/selectors'; +import joinUrlPaths from '@/utils/joinUrlPaths'; + +test.describe('...', () => { + test.beforeEach(async ({ page }) => { + const credentials = await AuthHelper.setupAuthWithEmailPassword( + page, + AccountCredentials.account2 + ); + + await AuthHelper.prepareBeforeTest( + page, + joinUrlPaths(GLOBAL_CONFIG.LOCAL_BASE_URL, ROUTES.DIRECT_FRIENDS), + credentials + ); + }); + + test('...', async ({ page }) => { + // ... + }); +}); +``` + +--- + +## 7) Seed test conventions (Planner & Generator) + +- Planner should always reference the seed: **`src/tests/web/seed.spec.ts`** +- Generator should follow the seed setup pattern (auth + navigation + waits) when applicable +- For no-auth flows (e.g., homepage), baseURL must still be valid; auth can be skipped. + +--- + +## 8) Naming conventions + +- Files: `kebab-case.spec.ts` +- Group by feature folder, e.g.: + - `src/tests/web/DirectMessage/send-simple-message.spec.ts` + - `src/tests/web/homepage/homepage-loads.spec.ts` + +--- + +## 9) Plan format guidelines (to keep Generator aligned) + +Each scenario should include: + +- **Seed:** `src/tests/web/seed.spec.ts` +- **File:** `src/tests/web/.../.spec.ts` +- **Steps:** numbered (1..n) +- **Expected Results:** clear bullets that can be asserted +- **Implementation hints (optional but recommended):** + - which Page Object / Selector class / Helper to use + - known `data-e2e` keys (optional) + +Example plan snippet: + +```md +### 1. Direct Message - Send Simple Message + +**Seed:** `src/tests/web/seed.spec.ts` +**File:** `src/tests/web/DirectMessage/send-simple-message.spec.ts` + +**Implementation hints (Project Context):** + +- Use `MessagePage` + `MessageTestHelpers` +- Prefer `MessageSelector` / `generateE2eSelector()` for locators + +**Steps:** + +1. Authenticate using seed pattern. +2. Navigate to Direct Friends page. +3. Create/open a DM conversation. +4. Send a unique text message. +5. Verify the message is visible in the message list. +``` + +--- + +## 10) Generator rules (copy/paste into prompt) + +- MUST use selector classes when available +- MUST prefer `generateE2eSelector()` over `getByText/getByRole` +- MUST follow auth/navigation setup pattern (or explicitly document “no-auth”) +- MUST save tests under `src/tests/web/` +- MUST keep **one test per file** + +--- + +## 11) Healer rules (repo-friendly) + +When healing failures: + +- Prefer switching to **data-e2e** locators or selector classes +- Reduce flakiness by waiting for locator states (visible/attached) instead of long sleeps +- Avoid unnecessary `networkidle` waits +- If feature is truly broken, use `test.fixme()` with a clear comment + +--- + +## 12) Quick checklist + +- [ ] `BASE_URL` is set (env/.env) +- [ ] Plans are saved under `specs/test-plans/` +- [ ] Plan `File:` paths start with `src/tests/web/...` +- [ ] Generator uses `data-e2e` (selector class / `generateE2eSelector`) +- [ ] Tests follow the seed setup pattern (when auth is required) diff --git a/src/tests/web/seed.spec.ts b/src/tests/web/seed.spec.ts new file mode 100644 index 00000000..d25f2246 --- /dev/null +++ b/src/tests/web/seed.spec.ts @@ -0,0 +1,23 @@ +import { test, expect } from '@playwright/test'; +import { AuthHelper } from '@/utils/authHelper'; +import { AccountCredentials, GLOBAL_CONFIG } from '@/config/environment'; +import { ROUTES } from '@/selectors'; +import joinUrlPaths from '@/utils/joinUrlPaths'; + +test.describe('Seed Test - Environment Setup', () => { + test('seed', async ({ page }) => { + const credentials = await AuthHelper.setupAuthWithEmailPassword( + page, + AccountCredentials.account2 + ); + + await AuthHelper.prepareBeforeTest( + page, + joinUrlPaths(GLOBAL_CONFIG.LOCAL_BASE_URL, ROUTES.DIRECT_FRIENDS), + credentials + ); + + await page.waitForLoadState('networkidle'); + await expect(page).toHaveURL(/.*direct.*/); + }); +}); diff --git a/yarn.lock b/yarn.lock index ea49b37f..a4fbe106 100644 --- a/yarn.lock +++ b/yarn.lock @@ -82,7 +82,7 @@ luxon "^3.5.0" xmlbuilder "^15.1.1" -"@cucumber/messages@>=19.1.4 <28", "@cucumber/messages@^27.0.0", "@cucumber/messages@^27.2.0": +"@cucumber/messages@*", "@cucumber/messages@^27.0.0", "@cucumber/messages@^27.2.0", "@cucumber/messages@>=18", "@cucumber/messages@>=19.1.4 <28": version "27.2.0" resolved "https://registry.npmjs.org/@cucumber/messages/-/messages-27.2.0.tgz" integrity sha512-f2o/HqKHgsqzFLdq6fAhfG1FNOQPdBdyMGpKwhb7hZqg0yZtx9BVqkTyuoNk83Fcvk3wjMVfouFXXHNEk4nddA== @@ -115,135 +115,10 @@ resolved "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-6.2.0.tgz" integrity sha512-KIF0eLcafHbWOuSDWFw0lMmgJOLdDRWjEL1kfXEWrqHmx2119HxVAr35WuEd9z542d3Yyg+XNqSr+81rIKqEdg== -"@esbuild/aix-ppc64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz#ee6b7163a13528e099ecf562b972f2bcebe0aa97" - integrity sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw== - -"@esbuild/android-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz#115fc76631e82dd06811bfaf2db0d4979c16e2cb" - integrity sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg== - -"@esbuild/android-arm@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz#8d5811912da77f615398611e5bbc1333fe321aa9" - integrity sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w== - -"@esbuild/android-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz#e3e96516b2d50d74105bb92594c473e30ddc16b1" - integrity sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg== - -"@esbuild/darwin-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz#6af6bb1d05887dac515de1b162b59dc71212ed76" - integrity sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA== - -"@esbuild/darwin-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz#99ae82347fbd336fc2d28ffd4f05694e6e5b723d" - integrity sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg== - -"@esbuild/freebsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz#0c6d5558a6322b0bdb17f7025c19bd7d2359437d" - integrity sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg== - -"@esbuild/freebsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz#8c35873fab8c0857a75300a3dcce4324ca0b9844" - integrity sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA== - -"@esbuild/linux-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz#3edc2f87b889a15b4cedaf65f498c2bed7b16b90" - integrity sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ== - -"@esbuild/linux-arm@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz#86501cfdfb3d110176d80c41b27ed4611471cde7" - integrity sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg== - -"@esbuild/linux-ia32@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz#e6589877876142537c6864680cd5d26a622b9d97" - integrity sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ== - -"@esbuild/linux-loong64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz#11119e18781f136d8083ea10eb6be73db7532de8" - integrity sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg== - -"@esbuild/linux-mips64el@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz#3052f5436b0c0c67a25658d5fc87f045e7def9e6" - integrity sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA== - -"@esbuild/linux-ppc64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz#2f098920ee5be2ce799f35e367b28709925a8744" - integrity sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA== - -"@esbuild/linux-riscv64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz#fa51d7fd0a22a62b51b4b94b405a3198cf7405dd" - integrity sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA== - -"@esbuild/linux-s390x@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz#a27642e36fc282748fdb38954bd3ef4f85791e8a" - integrity sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew== - -"@esbuild/linux-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz#9d9b09c0033d17529570ced6d813f98315dfe4e9" - integrity sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA== - -"@esbuild/netbsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz#25c09a659c97e8af19e3f2afd1c9190435802151" - integrity sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A== - -"@esbuild/netbsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz#7fa5f6ffc19be3a0f6f5fd32c90df3dc2506937a" - integrity sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig== - -"@esbuild/openbsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz#8faa6aa1afca0c6d024398321d6cb1c18e72a1c3" - integrity sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw== - -"@esbuild/openbsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz#a42979b016f29559a8453d32440d3c8cd420af5e" - integrity sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw== - -"@esbuild/openharmony-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz#fd87bfeadd7eeb3aa384bbba907459ffa3197cb1" - integrity sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag== - -"@esbuild/sunos-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz#3a18f590e36cb78ae7397976b760b2b8c74407f4" - integrity sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ== - -"@esbuild/win32-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz#e71741a251e3fd971408827a529d2325551f530c" - integrity sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw== - -"@esbuild/win32-ia32@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz#c6f010b5d3b943d8901a0c87ea55f93b8b54bf94" - integrity sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw== - -"@esbuild/win32-x64@0.25.10": - version "0.25.10" - resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz#e4b3e255a1b4aea84f6e1d2ae0b73f826c3785bd" - integrity sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw== +"@esbuild/darwin-arm64@0.25.12": + version "0.25.12" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz" + integrity sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0": version "4.7.0" @@ -378,7 +253,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -396,19 +271,19 @@ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@playwright/test@^1.55.0": - version "1.55.0" - resolved "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz" - integrity sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ== +"@playwright/test@^1.42.1", "@playwright/test@^1.57.0", "@playwright/test@>=1.36.0", "@playwright/test@>=1.42": + version "1.57.0" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz" + integrity sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA== dependencies: - playwright "1.55.0" + playwright "1.57.0" "@scarf/scarf@^1.1.1": version "1.4.0" - resolved "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" + resolved "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz" integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== -"@teppeis/multimaps@3.0.0", "@teppeis/multimaps@^3.0.0": +"@teppeis/multimaps@^3.0.0", "@teppeis/multimaps@3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-3.0.0.tgz" integrity sha512-ID7fosbc50TbT0MK0EG12O+gAP3W3Aa/Pz4DaTtQtEvlc9Odaqi0de+xuZ7Li2GtK4HzEX7IuRWS/JmZLksR3Q== @@ -464,7 +339,7 @@ natural-compare "^1.4.0" ts-api-utils "^2.1.0" -"@typescript-eslint/parser@^8.0.0": +"@typescript-eslint/parser@^8.0.0", "@typescript-eslint/parser@^8.38.0": version "8.38.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz" integrity sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ== @@ -492,7 +367,7 @@ "@typescript-eslint/types" "8.38.0" "@typescript-eslint/visitor-keys" "8.38.0" -"@typescript-eslint/tsconfig-utils@8.38.0", "@typescript-eslint/tsconfig-utils@^8.38.0": +"@typescript-eslint/tsconfig-utils@^8.38.0", "@typescript-eslint/tsconfig-utils@8.38.0": version "8.38.0" resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz" integrity sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ== @@ -508,7 +383,7 @@ debug "^4.3.4" ts-api-utils "^2.1.0" -"@typescript-eslint/types@8.38.0", "@typescript-eslint/types@^8.38.0": +"@typescript-eslint/types@^8.38.0", "@typescript-eslint/types@8.38.0": version "8.38.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz" integrity sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw== @@ -559,7 +434,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.15.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0: version "8.15.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -579,14 +454,14 @@ allure-commandline@^2.34.1: resolved "https://registry.npmjs.org/allure-commandline/-/allure-commandline-2.34.1.tgz" integrity sha512-l42csZ2bz7FdtJI1t5zA3IXtOZ0YJaP/+JMRC9gt6aBHRVUIu+6r+3F7KRyshQ79osLz9/MHlGqAjBPRqH0QFw== -allure-js-commons@3.3.3, allure-js-commons@^3.3.3: +allure-js-commons@^3.3.3, allure-js-commons@3.3.3: version "3.3.3" resolved "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-3.3.3.tgz" integrity sha512-MLkPWrVtrt/2TdAvaExNSM8yTuY/lEo+MSLoM2DOUUsWzbzki8XIxHoX+mSdkatZAJargsU9JeO/dL5kQyR5IQ== dependencies: md5 "^2.3.0" -allure-playwright@^3.3.3: +allure-playwright@^3.3.3, allure-playwright@3.3.3: version "3.3.3" resolved "https://registry.npmjs.org/allure-playwright/-/allure-playwright-3.3.3.tgz" integrity sha512-f5+PsYJVBG9igcD2ldzRHOx4kMiuqyoezpneQd94R08I/S3lL/MvdMNmkT1vet8KYB8tsV1OuK17PZexnFaC+w== @@ -594,9 +469,9 @@ allure-playwright@^3.3.3: allure-js-commons "3.3.3" ansi-escapes@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.1.0.tgz#91983a524b64e49f8e46fb962bfb7f375ced2ad5" - integrity sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g== + version "7.2.0" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz" + integrity sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw== dependencies: environment "^1.0.0" @@ -617,7 +492,17 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.0.0, ansi-styles@^6.1.0, ansi-styles@^6.2.1: +ansi-styles@^6.0.0: + version "6.2.3" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +ansi-styles@^6.2.1: version "6.2.3" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz" integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== @@ -713,7 +598,7 @@ bare-events@^2.2.0: base64-arraybuffer@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz" integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== base64-js@^1.0.2, base64-js@^1.3.1: @@ -758,6 +643,14 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@4.9.2: version "4.9.2" resolved "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz" @@ -767,14 +660,6 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" @@ -816,7 +701,7 @@ chalk@^4.0.0: chalk@^5.4.1: version "5.6.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + resolved "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz" integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== charenc@0.0.2: @@ -846,7 +731,7 @@ class-transformer@0.5.1: cli-cursor@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz" integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== dependencies: restore-cursor "^5.0.0" @@ -862,7 +747,7 @@ cli-table3@0.6.5: cli-truncate@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz" integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== dependencies: slice-ansi "^5.0.0" @@ -882,10 +767,10 @@ color-name@~1.1.4: colorette@^2.0.20: version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== -commander@13.1.0, commander@^13.1.0: +commander@^13.1.0, commander@13.1.0: version "13.1.0" resolved "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz" integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== @@ -926,7 +811,7 @@ crc32-stream@^6.0.0: cross-spawn@^6.0.0: version "6.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz" integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw== dependencies: nice-try "^1.0.4" @@ -951,23 +836,16 @@ crypt@0.0.2: crypto@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" + resolved "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz" integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== -debug@4.4.3, debug@^4.4.0: +debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@4.4.3: version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" -debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.4.1" - resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== - dependencies: - ms "^2.1.3" - deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -1002,9 +880,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== emoji-regex@^10.3.0: - version "10.5.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.5.0.tgz#be23498b9e39db476226d8e81e467f39aca26b78" - integrity sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg== + version "10.6.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz" + integrity sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== emoji-regex@^8.0.0: version "8.0.0" @@ -1018,7 +896,7 @@ emoji-regex@^9.2.2: end-of-stream@^1.1.0: version "1.4.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz" integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== dependencies: once "^1.4.0" @@ -1030,57 +908,57 @@ entities@^2.2.0: environment@^1.0.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + resolved "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz" integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" - resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" esbuild@^0.25.5: - version "0.25.10" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz#37f5aa5cd14500f141be121c01b096ca83ac34a9" - integrity sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ== + version "0.25.12" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz" + integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg== optionalDependencies: - "@esbuild/aix-ppc64" "0.25.10" - "@esbuild/android-arm" "0.25.10" - "@esbuild/android-arm64" "0.25.10" - "@esbuild/android-x64" "0.25.10" - "@esbuild/darwin-arm64" "0.25.10" - "@esbuild/darwin-x64" "0.25.10" - "@esbuild/freebsd-arm64" "0.25.10" - "@esbuild/freebsd-x64" "0.25.10" - "@esbuild/linux-arm" "0.25.10" - "@esbuild/linux-arm64" "0.25.10" - "@esbuild/linux-ia32" "0.25.10" - "@esbuild/linux-loong64" "0.25.10" - "@esbuild/linux-mips64el" "0.25.10" - "@esbuild/linux-ppc64" "0.25.10" - "@esbuild/linux-riscv64" "0.25.10" - "@esbuild/linux-s390x" "0.25.10" - "@esbuild/linux-x64" "0.25.10" - "@esbuild/netbsd-arm64" "0.25.10" - "@esbuild/netbsd-x64" "0.25.10" - "@esbuild/openbsd-arm64" "0.25.10" - "@esbuild/openbsd-x64" "0.25.10" - "@esbuild/openharmony-arm64" "0.25.10" - "@esbuild/sunos-x64" "0.25.10" - "@esbuild/win32-arm64" "0.25.10" - "@esbuild/win32-ia32" "0.25.10" - "@esbuild/win32-x64" "0.25.10" + "@esbuild/aix-ppc64" "0.25.12" + "@esbuild/android-arm" "0.25.12" + "@esbuild/android-arm64" "0.25.12" + "@esbuild/android-x64" "0.25.12" + "@esbuild/darwin-arm64" "0.25.12" + "@esbuild/darwin-x64" "0.25.12" + "@esbuild/freebsd-arm64" "0.25.12" + "@esbuild/freebsd-x64" "0.25.12" + "@esbuild/linux-arm" "0.25.12" + "@esbuild/linux-arm64" "0.25.12" + "@esbuild/linux-ia32" "0.25.12" + "@esbuild/linux-loong64" "0.25.12" + "@esbuild/linux-mips64el" "0.25.12" + "@esbuild/linux-ppc64" "0.25.12" + "@esbuild/linux-riscv64" "0.25.12" + "@esbuild/linux-s390x" "0.25.12" + "@esbuild/linux-x64" "0.25.12" + "@esbuild/netbsd-arm64" "0.25.12" + "@esbuild/netbsd-x64" "0.25.12" + "@esbuild/openbsd-arm64" "0.25.12" + "@esbuild/openbsd-x64" "0.25.12" + "@esbuild/openharmony-arm64" "0.25.12" + "@esbuild/sunos-x64" "0.25.12" + "@esbuild/win32-arm64" "0.25.12" + "@esbuild/win32-ia32" "0.25.12" + "@esbuild/win32-x64" "0.25.12" escape-string-regexp@^4.0.0: version "4.0.0" @@ -1089,12 +967,12 @@ escape-string-regexp@^4.0.0: eslint-formatter-sonarqube@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/eslint-formatter-sonarqube/-/eslint-formatter-sonarqube-1.0.0.tgz#0bdbe7322eec6117dd4eb1f67662bf5ad8abcd91" + resolved "https://registry.npmjs.org/eslint-formatter-sonarqube/-/eslint-formatter-sonarqube-1.0.0.tgz" integrity sha512-6cTJeUfQm7F5saT6y1jvFNNYPPJyQd1UJOYbPTCSz66gBanPKc0BSANt5m/pK7UIRtjhCatK6L8tIfJ6SnhNWw== eslint-plugin-sonarjs@^1.0.0: version "1.0.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-1.0.4.tgz#9a3b0221911050e3c93f83535bd87ef915d5b5dc" + resolved "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-1.0.4.tgz" integrity sha512-jF0eGCUsq/HzMub4ExAyD8x1oEgjOyB9XVytYGyWgSFvdiJQJp6IuP7RmtauCf06o6N/kZErh+zW4b10y1WZ+Q== eslint-scope@^8.4.0: @@ -1115,7 +993,7 @@ eslint-visitor-keys@^4.2.1: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== -eslint@^9.0.0: +"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.0.0 || ^9.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.0.0: version "9.32.0" resolved "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz" integrity sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg== @@ -1196,35 +1074,22 @@ event-target-shim@^5.0.0: eventemitter3@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -events@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== - events@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -execa@0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.11.0.tgz#0b3c71daf9b9159c252a863cd981af1b4410d97a" - integrity sha512-k5AR22vCt1DcfeiRixW46U5tMLtBg44ssdJM9PiXw3D8Bn5qyxFCSnKY/eR22y+ctFDGPqafpaXg2G4Emyua4A== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" +events@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz" + integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw== execa@^8.0.1: version "8.0.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz" integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== dependencies: cross-spawn "^7.0.3" @@ -1237,6 +1102,19 @@ execa@^8.0.1: signal-exit "^4.1.0" strip-final-newline "^3.0.0" +execa@0.11.0: + version "0.11.0" + resolved "https://registry.npmjs.org/execa/-/execa-0.11.0.tgz" + integrity sha512-k5AR22vCt1DcfeiRixW46U5tMLtBg44ssdJM9PiXw3D8Bn5qyxFCSnKY/eR22y+ctFDGPqafpaXg2G4Emyua4A== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -1325,24 +1203,24 @@ foreground-child@^3.1.0: cross-spawn "^7.0.6" signal-exit "^4.0.1" -fsevents@2.3.2, fsevents@~2.3.2: +fsevents@~2.3.2, fsevents@2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== get-east-asian-width@^1.0.0, get-east-asian-width@^1.3.1: version "1.4.0" - resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz#9bc4caa131702b4b61729cb7e42735bc550c9ee6" + resolved "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz" integrity sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q== get-intrinsic@^1.2.4, get-intrinsic@^1.3.0: version "1.3.0" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: call-bind-apply-helpers "^1.0.2" @@ -1358,7 +1236,7 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.3.0: get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: dunder-proto "^1.0.1" @@ -1366,17 +1244,17 @@ get-proto@^1.0.0, get-proto@^1.0.1: get-stream@^4.0.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" get-stream@^8.0.1: version "8.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz" integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -1390,6 +1268,13 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob@^10.0.0: version "10.4.5" resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz" @@ -1434,33 +1319,33 @@ has-flag@^4.0.0: has-property-descriptors@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: es-define-property "^1.0.0" has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" hasown@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" human-signals@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== husky@^9.1.7: @@ -1468,7 +1353,7 @@ husky@^9.1.7: resolved "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz" integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== -ieee754@1.1.13, ieee754@^1.1.4: +ieee754@^1.1.4, ieee754@1.1.13: version "1.1.13" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== @@ -1548,19 +1433,19 @@ is-fullwidth-code-point@^3.0.0: is-fullwidth-code-point@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz" integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== is-fullwidth-code-point@^5.0.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz#046b2a6d4f6b156b2233d3207d4b5a9783999b98" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz" integrity sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== dependencies: get-east-asian-width "^1.3.1" is-generator-function@^1.0.7: version "1.1.0" - resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz" integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== dependencies: call-bound "^1.0.3" @@ -1582,7 +1467,7 @@ is-number@^7.0.0: is-regex@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz" integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== dependencies: call-bound "^1.0.2" @@ -1592,7 +1477,7 @@ is-regex@^1.2.1: is-stream@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== is-stream@^2.0.1: @@ -1602,12 +1487,12 @@ is-stream@^2.0.1: is-stream@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz" integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== is-typed-array@^1.1.3: version "1.1.15" - resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz" integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== dependencies: which-typed-array "^1.1.16" @@ -1638,7 +1523,7 @@ jmespath@0.16.0: js-base64@^3.7.4: version "3.7.8" - resolved "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz#af44496bc09fa178ed9c4adf67eb2b46f5c6d2a4" + resolved "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz" integrity sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow== js-yaml@^4.1.0: @@ -1687,12 +1572,12 @@ levn@^0.4.1: lilconfig@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz" integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== lint-staged@^15.2.10: version "15.5.2" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.5.2.tgz#beff028fd0681f7db26ffbb67050a21ed4d059a3" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz" integrity sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w== dependencies: chalk "^5.4.1" @@ -1708,7 +1593,7 @@ lint-staged@^15.2.10: listr2@^8.2.5: version "8.3.3" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.3.3.tgz#815fc8f738260ff220981bf9e866b3e11e8121bf" + resolved "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz" integrity sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ== dependencies: cli-truncate "^4.0.0" @@ -1742,7 +1627,7 @@ lodash@^4.17.15: log-update@^6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" + resolved "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz" integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== dependencies: ansi-escapes "^7.0.0" @@ -1782,7 +1667,7 @@ md5@^2.3.0: merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0: @@ -1791,9 +1676,9 @@ merge2@^1.3.0: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== mezon-js@^2.13.16: - version "2.13.16" - resolved "https://registry.npmjs.org/mezon-js/-/mezon-js-2.13.16.tgz#82da11ef75cb5f1e150f3ee246f840f00403cfbc" - integrity sha512-E3oHo4QUSH92Pyb3xL5+9wxVblDoeJO9XJ/NF8GPQBgKOWovSmnezzURZPGjqFjvaUXUjKSzXPbQ0XlrluOkVg== + version "2.13.79" + resolved "https://registry.npmjs.org/mezon-js/-/mezon-js-2.13.79.tgz" + integrity sha512-ilDzKDwT7nCAY6PA8qyehcIVnMXHSa665l9IHdbRSghRdd0kishsNCswAOK+vYCegJvNfyEWOmml8kLU6r53bg== dependencies: "@scarf/scarf" "^1.1.1" base64-arraybuffer "^1.0.2" @@ -1823,12 +1708,12 @@ mime-types@^3.0.1: mimic-fn@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== mimic-function@^5.0.0: version "5.0.1" - resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + resolved "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== minimatch@^3.1.2: @@ -1854,7 +1739,7 @@ minimatch@^9.0.4: minimist@1.2.8: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: @@ -1874,7 +1759,7 @@ natural-compare@^1.4.0: nice-try@^1.0.4: version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== nodemon@^3.1.10: @@ -1900,35 +1785,35 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: npm-run-path@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz" integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== dependencies: path-key "^2.0.0" npm-run-path@^5.1.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz" integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== dependencies: path-key "^4.0.0" once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + resolved "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz" integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== dependencies: mimic-fn "^4.0.0" onetime@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + resolved "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz" integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== dependencies: mimic-function "^5.0.0" @@ -1947,7 +1832,7 @@ optionator@^0.9.3: p-finally@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== p-limit@^3.0.2: @@ -1983,7 +1868,7 @@ path-exists@^4.0.0: path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz" integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== path-key@^3.1.0: @@ -1993,7 +1878,7 @@ path-key@^3.1.0: path-key@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + resolved "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz" integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== path-scurry@^1.11.1: @@ -2011,7 +1896,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: pidtree@^0.6.0: version "0.6.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + resolved "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== playwright-bdd@^8.3.1: @@ -2032,17 +1917,17 @@ playwright-bdd@^8.3.1: mime-types "^3.0.1" xmlbuilder "15.1.1" -playwright-core@1.55.0: - version "1.55.0" - resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz" - integrity sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg== +playwright-core@1.57.0: + version "1.57.0" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz" + integrity sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ== -playwright@1.55.0: - version "1.55.0" - resolved "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz" - integrity sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA== +playwright@1.57.0: + version "1.57.0" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz" + integrity sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw== dependencies: - playwright-core "1.55.0" + playwright-core "1.57.0" optionalDependencies: fsevents "2.3.2" @@ -2078,22 +1963,22 @@ pstree.remy@^1.1.8: pump@^3.0.0: version "3.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz" integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== dependencies: end-of-stream "^1.1.0" once "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== - punycode@^2.1.0: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + querystring@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" @@ -2166,7 +2051,7 @@ resolve-from@^4.0.0: restore-cursor@^5.0.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz" integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== dependencies: onetime "^7.0.0" @@ -2179,7 +2064,7 @@ reusify@^1.0.4: rfdc@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz" integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== run-parallel@^1.1.9: @@ -2201,26 +2086,21 @@ safe-buffer@~5.2.0: safe-regex-test@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz" integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== dependencies: call-bound "^1.0.2" es-errors "^1.3.0" is-regex "^1.2.1" -sax@1.2.1: +sax@>=0.6.0, sax@1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== -sax@>=0.6.0: - version "1.4.1" - resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" - integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== - semver@^5.5.0: version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^7.5.3, semver@^7.6.0: @@ -2230,7 +2110,7 @@ semver@^7.5.3, semver@^7.6.0: set-function-length@^1.2.2: version "1.2.2" - resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: define-data-property "^1.1.4" @@ -2242,7 +2122,7 @@ set-function-length@^1.2.2: shebang-command@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== dependencies: shebang-regex "^1.0.0" @@ -2256,7 +2136,7 @@ shebang-command@^2.0.0: shebang-regex@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== shebang-regex@^3.0.0: @@ -2266,7 +2146,7 @@ shebang-regex@^3.0.0: signal-exit@^3.0.0: version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== signal-exit@^4.0.1, signal-exit@^4.1.0: @@ -2283,7 +2163,7 @@ simple-update-notifier@^2.0.0: slice-ansi@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz" integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== dependencies: ansi-styles "^6.0.0" @@ -2291,7 +2171,7 @@ slice-ansi@^5.0.0: slice-ansi@^7.1.0: version "7.1.2" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.2.tgz#adf7be70aa6d72162d907cd0e6d5c11f507b5403" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz" integrity sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w== dependencies: ansi-styles "^6.2.1" @@ -2312,7 +2192,7 @@ source-map@^0.6.0: stop-only@^3.1.2: version "3.4.4" - resolved "https://registry.yarnpkg.com/stop-only/-/stop-only-3.4.4.tgz#dcf7b443e7ea8e487f8ca4182b73fba9b8309f44" + resolved "https://registry.npmjs.org/stop-only/-/stop-only-3.4.4.tgz" integrity sha512-fWpzRixI9sM8fnldjMMGha0ckjUHghedXB+WWaEhFypdeRPiA3rbM8tlyKzb/hb7S672AO78I7/fvlSRQ0fAPw== dependencies: debug "4.4.3" @@ -2329,9 +2209,23 @@ streamx@^2.15.0: optionalDependencies: bare-events "^2.2.0" +string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + string-argv@^0.3.2: version "0.3.2" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== "string-width-cjs@npm:string-width@^4.2.0": @@ -2352,7 +2246,16 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: +string-width@^5.0.1: + version "5.1.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string-width@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -2363,35 +2266,28 @@ string-width@^5.0.1, string-width@^5.1.2: string-width@^7.0.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + resolved "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz" integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== dependencies: emoji-regex "^10.3.0" get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" -string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - safe-buffer "~5.1.0" + ansi-regex "^5.0.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +strip-ansi@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2407,12 +2303,12 @@ strip-ansi@^7.0.1, strip-ansi@^7.1.0: strip-eof@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz" integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== strip-final-newline@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== strip-json-comments@^3.1.1: @@ -2479,7 +2375,7 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -typescript@^5.5.0: +typescript@^5.5.0, typescript@>=4.8.4, "typescript@>=4.8.4 <5.9.0": version "5.8.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz" integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== @@ -2544,17 +2440,17 @@ uuid@11.0.5: uuid@8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz" integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== whatwg-fetch@^3.6.2: version "3.6.20" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz" integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== which-typed-array@^1.1.16, which-typed-array@^1.1.2: version "1.1.19" - resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz" integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== dependencies: available-typed-arrays "^1.0.7" @@ -2567,7 +2463,7 @@ which-typed-array@^1.1.16, which-typed-array@^1.1.2: which@^1.2.9: version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" @@ -2604,7 +2500,7 @@ wrap-ansi@^8.1.0: wrap-ansi@^9.0.0: version "9.0.2" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz" integrity sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== dependencies: ansi-styles "^6.2.1" @@ -2613,31 +2509,31 @@ wrap-ansi@^9.0.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== xml2js@0.6.2: version "0.6.2" - resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz" integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" -xmlbuilder@15.1.1, xmlbuilder@^15.1.1: +xmlbuilder@^15.1.1, xmlbuilder@15.1.1: version "15.1.1" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== xmlbuilder@~11.0.0: version "11.0.1" - resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== yaml@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" - integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== + version "2.8.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz" + integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A== yocto-queue@^0.1.0: version "0.1.0" From 87c4a9dc53374347321239533715a5b29083ce30 Mon Sep 17 00:00:00 2001 From: "trinh.truongthiphuong" Date: Tue, 10 Feb 2026 11:58:17 +0700 Subject: [PATCH 2/2] add test cases --- src/data/selectors/ClanSelector.ts | 112 +++++ src/data/selectors/MessageSelector.ts | 54 +++ src/data/selectors/ProfileSelector.ts | 7 + src/pages/ChannelSettingPage.ts | 9 + src/pages/Clan/ClanPage.ts | 115 ++++- src/pages/FriendPage.ts | 4 + src/pages/HomePage.ts | 2 +- src/pages/MessagePage.ts | 78 +++- src/pages/OnboardingPage.ts | 128 +++++- src/pages/ProfilePage.ts | 87 +++- src/selectors/index.ts | 70 ++- .../ChannelMessage/ChannelMessage3.spec.ts | 195 ++++++++ .../ChannelMessage/ChannelMessage4.spec.ts | 417 ++++++++++++++++++ .../ClanManagement/ClanManagement2.spec.ts | 127 ++++++ .../{ => DirectMessage}/DirectMessage.spec.ts | 63 ++- .../DirectMessage/DirectMessage2.spec.ts | 304 +++++++++++++ .../ChannelManagement_3.spec.ts | 57 +++ .../ChannelMessage/ChannelMessage_8.spec.ts | 151 +++++++ .../ChannelMessage/channelMessage_7.spec.ts | 119 +++++ .../{ => DirectMessage}/DirectMessage.spec.ts | 0 .../web/DirectMessage/DirectMessage1.spec.ts | 170 +++++++ src/tests/web/OnboardingGuide.spec.ts | 191 ++++++++ src/utils/clanSetupHelper.ts | 10 + src/utils/messageHelpers.ts | 106 +++++ 24 files changed, 2562 insertions(+), 14 deletions(-) create mode 100644 src/tests/multi-chat/ChannelMessage/ChannelMessage4.spec.ts rename src/tests/multi-chat/{ => DirectMessage}/DirectMessage.spec.ts (85%) create mode 100644 src/tests/multi-chat/DirectMessage/DirectMessage2.spec.ts create mode 100644 src/tests/web/ChannelMessage/ChannelMessage_8.spec.ts rename src/tests/web/{ => DirectMessage}/DirectMessage.spec.ts (100%) create mode 100644 src/tests/web/DirectMessage/DirectMessage1.spec.ts diff --git a/src/data/selectors/ClanSelector.ts b/src/data/selectors/ClanSelector.ts index 7c3c0ee7..ff623e3c 100644 --- a/src/data/selectors/ClanSelector.ts +++ b/src/data/selectors/ClanSelector.ts @@ -56,6 +56,9 @@ export default class ClanSelector { hasText: 'Mark as Read', }), badge: this.page.locator(generateE2eSelector('clan_page.badge')), + preventAnoSettings: this.page.locator( + `${generateE2eSelector('clan_page.settings.overview.prevent_anonymous')} ${generateE2eSelector('input.base')}` + ), }; readonly sidebarMemberList = { @@ -75,6 +78,11 @@ export default class ClanSelector { .filter({ hasText: 'Kick', }), + shareContactButton: this.page + .locator(generateE2eSelector('chat.channel_message.member_list.item.actions')) + .filter({ + hasText: 'Share this contact', + }), }; readonly sidePanel = { @@ -91,6 +99,18 @@ export default class ClanSelector { generateE2eSelector('clan_page.member_list.user_info.display_name') ), username: this.page.locator(generateE2eSelector('clan_page.member_list.user_info.username')), + actionsButton: this.page.locator(generateE2eSelector('clan_page.member_list.actions')), + transferOwnershipModal: { + container: this.page.locator( + generateE2eSelector('clan_page.member_list.transfer_owner_modal') + ), + confirmTransferInput: this.page.locator( + generateE2eSelector('clan_page.member_list.transfer_owner_modal.input.confirm_transfer') + ), + confirmTransferButton: this.page.locator(generateE2eSelector('button.base'), { + hasText: 'Transfer Ownership', + }), + }, }; readonly footerProfile = { @@ -361,6 +381,7 @@ export default class ClanSelector { generateE2eSelector('clan_page.secondary_side_bar.member.user_status') ), username: this.page.locator(generateE2eSelector('chat.direct_message.chat_item.username')), + ownerIcon: this.page.locator(generateE2eSelector('icon.owner')), }, }; @@ -429,6 +450,9 @@ export default class ClanSelector { generateE2eSelector('clan_page.modal.create_event.location.input') ), }, + selectVoiceChannel: this.page.locator( + `${generateE2eSelector('clan_page.modal.create_event.location')} div:has-text("Select Voice channel")` + ), selectChannel: this.page.locator( `${generateE2eSelector('clan_page.modal.create_event.location')} div:has-text("Select channel")` ), @@ -562,6 +586,94 @@ export default class ClanSelector { }, }; + readonly onboarding = { + status: this.page.locator(generateE2eSelector('clan_page.settings.sidebar.onboarding_status')), + buttons: { + enableOnboarding: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.enable_onboarding') + ), + back: this.page.locator(generateE2eSelector('clan_page.settings.onboarding.button.back')), + disableOnboarding: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.disable_onboarding') + ), + openPreviewMode: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.open_preview_mode') + ), + closePreviewMode: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.close_preview_mode') + ), + }, + setupQuestion: { + item: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.setup_question') + ), + input: { + question: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.input.question') + ), + answerTitle: this.page.locator(` + ${generateE2eSelector('clan_page.settings.onboarding.input.answer')} input[placeholder="Enter an answer..."] + `), + answerDescription: this.page.locator(` + ${generateE2eSelector('clan_page.settings.onboarding.input.answer')} input[placeholder="Enter a description... (optional)"] + `), + }, + button: { + confirmAnswer: this.page.locator(generateE2eSelector('button.base'), { hasText: 'Save' }), + saveQuestion: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.save_change') + ), + addQuestion: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.add_question') + ), + addAnswer: this.page.locator(generateE2eSelector('button.base'), { + hasText: 'Add an Answer', + }), + removeQuestion: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.remove_question') + ), + removeAnswer: this.page.locator(generateE2eSelector('button.base'), { hasText: 'Remove' }), + questionItem: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.question.item') + ), + saveAll: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.save_all') + ), + }, + }, + clanGuideSettings: { + item: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.clan_guide') + ), + buttons: { + addTask: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.button.add_task') + ), + }, + input: { + taskTitle: this.page.locator( + generateE2eSelector('clan_page.settings.onboarding.clan_guide.input.title') + ), + }, + }, + clanGuidePage: { + sidebar: this.page.locator(generateE2eSelector('clan_page.side_bar.button.clan_guide')), + questionLabel: this.page.locator(generateE2eSelector('onboarding.clan_guide_page.label'), { + hasText: 'Questions', + }), + resourceLabel: this.page.locator(generateE2eSelector('onboarding.clan_guide_page.label'), { + hasText: 'Resources', + }), + missionLabel: this.page.locator(generateE2eSelector('onboarding.clan_guide_page.label'), { + hasText: 'Missions', + }), + title: this.page.locator(generateE2eSelector('onboarding.clan_guide_page.title')), + description: this.page.locator(generateE2eSelector('onboarding.clan_guide_page.description')), + action: this.page.locator(generateE2eSelector('onboarding.clan_guide_page.action')), + question: this.page.locator(generateE2eSelector('onboarding.clan_guide_page.question')), + }, + }; + /** * Find a clan item by its title attribute * @param clanName The exact title of the clan to find diff --git a/src/data/selectors/MessageSelector.ts b/src/data/selectors/MessageSelector.ts index 2eac1288..71664639 100644 --- a/src/data/selectors/MessageSelector.ts +++ b/src/data/selectors/MessageSelector.ts @@ -73,6 +73,19 @@ export default class MessageSelector { pinMessageButton = this.page .locator(generateE2eSelector('chat.message_action_modal.button.base')) .filter({ hasText: 'Pin message' }); + + unpinMessageButton = this.page + .locator(generateE2eSelector('chat.message_action_modal.button.base')) + .filter({ hasText: 'Unpin Message' }); + + addToInboxButton = this.page + .locator(generateE2eSelector('chat.message_action_modal.button.base')) + .filter({ hasText: 'Add To Inbox' }); + + markAsUnreadButton = this.page + .locator(generateE2eSelector('chat.message_action_modal.button.base')) + .filter({ hasText: 'Mark Unread' }); + confirmPinMessageButton = this.page.locator( generateE2eSelector('chat.message_action_modal.confirm_modal.button.confirm'), { hasText: 'Oh yeah. Pin it' } @@ -98,6 +111,10 @@ export default class MessageSelector { .locator(generateE2eSelector('chat.message_action_modal.button.base')) .filter({ hasText: 'Forward Message' }); + forwardAllMessagesButton = this.page + .locator(generateE2eSelector('chat.message_action_modal.button.base')) + .filter({ hasText: 'Forward All Messages' }); + createThreadButton = this.page .locator(generateE2eSelector('chat.message_action_modal.button.base')) .filter({ hasText: 'Create Thread' }); @@ -115,9 +132,16 @@ export default class MessageSelector { pinnedMessages = this.page.locator(generateE2eSelector('common.pin_message')); welcomeDM = this.page.locator(generateE2eSelector('chat_welcome')); welcomeDMAvatar = this.welcomeDM.locator(generateE2eSelector('avatar.image')); + headerDM = this.page.locator(generateE2eSelector('chat.direct_message.header.left_container')); headerDMAvatar = this.page.locator( `${generateE2eSelector('chat.direct_message.header.left_container')} ${generateE2eSelector('avatar.image')}` ); + invoiceStatusDMHeader = this.page.locator( + generateE2eSelector('chat.direct_message.header.left_container.in_voice_status') + ); + invoiceStatusFriendList = this.page.locator( + generateE2eSelector('chat.direct_message.chat_item.in_voice_status') + ); headerUserProfileButton = this.page.locator( `${generateE2eSelector('chat.direct_message.header.right_container.user_profile')}` ); @@ -225,6 +249,16 @@ export default class MessageSelector { headerGalleryButton = this.page.locator( generateE2eSelector('chat.channel_message.header.button.gallery') ); + waveToSayHiButton = this.page.locator(generateE2eSelector('chat.button.wave_to_say_hi')); + + readonly anonymous = { + anonymousIcon: this.page.locator(generateE2eSelector('chat.anonymous')), + anonymousMessage: this.page.locator(generateE2eSelector('base_profile.anonymous')), + anonymousAvatar: this.page.locator(generateE2eSelector('base_profile.anonymous.avatar')), + anonymousName: this.page.locator(generateE2eSelector('base_profile.display_name'), { + hasText: 'Anonymous', + }), + }; readonly shortProfile = { avatar: this.page.locator( @@ -298,5 +332,25 @@ export default class MessageSelector { }), }; + readonly messageInboxPopover = { + triggerTab: this.page.locator(generateE2eSelector('chat.channel_message.inbox.action_tabs'), { + hasText: 'Messages', + }), + }; + + readonly shareContact = { + card: this.page.locator(generateE2eSelector('chat.share_contact')), + displayName: this.page.locator(generateE2eSelector('chat.share_contact.display_name')), + username: this.page.locator(generateE2eSelector('chat.share_contact.username')), + buttonCall: this.page.locator(generateE2eSelector('chat.share_contact.button.call')), + buttonMessage: this.page.locator(generateE2eSelector('chat.share_contact.button.message')), + modal: { + item: this.page.locator(generateE2eSelector('modal.share_contact')), + inputSearch: this.page.locator(generateE2eSelector('modal.share_contact.input.search')), + buttonCancel: this.page.locator(generateE2eSelector('modal.share_contact.button.cancel')), + buttonShare: this.page.locator(generateE2eSelector('modal.share_contact.button.share')), + }, + }; + mentionUser = this.page.locator(generateE2eSelector('chat.channel_message.mention_user')); } diff --git a/src/data/selectors/ProfileSelector.ts b/src/data/selectors/ProfileSelector.ts index db7de512..b43a0c81 100644 --- a/src/data/selectors/ProfileSelector.ts +++ b/src/data/selectors/ProfileSelector.ts @@ -102,6 +102,9 @@ export default class ProfileSelector { }), }, modal: { + container: this.page.locator( + generateE2eSelector('user_setting.profile.user_profile.preview.avatar') + ), customStatus: { container: this.page.locator(generateE2eSelector('short_profile.modal.custom_status')), input: this.page.locator(generateE2eSelector('short_profile.modal.custom_status.input')), @@ -126,5 +129,9 @@ export default class ProfileSelector { ), }, }, + profileStatus: { + triggerButton: this.page.locator(generateE2eSelector('short_profile.action.button.base')), + status: this.page.locator(generateE2eSelector('icon.profile_status')), + }, }; } diff --git a/src/pages/ChannelSettingPage.ts b/src/pages/ChannelSettingPage.ts index 444a5b5d..43990070 100644 --- a/src/pages/ChannelSettingPage.ts +++ b/src/pages/ChannelSettingPage.ts @@ -281,4 +281,13 @@ export class ChannelSettingPage extends BasePage { async openMemberList() { await this.selector.header.button.member.first().click(); } + + async isPermissionSettingsVisible(): Promise { + try { + await this.selector.side_bar_buttons.permissions.waitFor({ state: 'visible' }); + return true; + } catch { + return false; + } + } } diff --git a/src/pages/Clan/ClanPage.ts b/src/pages/Clan/ClanPage.ts index 027cf0e5..8fb6e0db 100644 --- a/src/pages/Clan/ClanPage.ts +++ b/src/pages/Clan/ClanPage.ts @@ -195,6 +195,13 @@ export class ClanPage extends BasePage { } } + async preventAnonymous() { + const buttonSettings = this.selector.buttons.preventAnoSettings; + await buttonSettings.click(); + await this.selector.buttons.saveChanges.click(); + await expect(this.selector.buttons.saveChanges).toBeHidden({ timeout: 5000 }); + } + async createEvent(): Promise { this.selector.buttons.eventButton.click(); this.selector.eventModal.createEventButton.click(); @@ -484,7 +491,7 @@ export class ClanPage extends BasePage { } if (voiceChannelName) { if (eventType === EventType.VOICE) { - await this.selector.createEventModal.selectChannel.first().click(); + await this.selector.createEventModal.selectVoiceChannel.first().click(); const channelItem = this.selector.createEventModal.channelItem.filter({ hasText: voiceChannelName, }); @@ -1707,4 +1714,110 @@ export class ClanPage extends BasePage { const aboutMeStatusLocator = this.selector.modal.aboutMe; await expect(aboutMeStatusLocator).toHaveText(status, { timeout: 2000 }); } + + async clickShareContactByName(username: string) { + const memberLocator = this.selector.sidebarMemberList.memberItems + .filter({ hasText: username }) + .first(); + await expect(memberLocator).toBeVisible({ timeout: 3000 }); + await memberLocator.click({ button: 'right' }); + + await this.selector.sidebarMemberList.shareContactButton.click(); + } + + async openMemberActionsMenu(username: string) { + const userRow = this.page.locator( + `${generateE2eSelector('clan_page.member_list')}:has(${generateE2eSelector('clan_page.member_list.user_info.username')}:has-text("${username}"))` + ); + await expect(userRow).toBeVisible({ timeout: 5000 }); + await userRow.click({ button: 'right' }); + } + + async clickTransferClanOwnershipButton() { + const transferOwnershipButton = this.selector.memberSettings.actionsButton.locator( + generateE2eSelector('chat.direct_message.menu.leave_group.button'), + { hasText: 'Transfer Ownership' } + ); + await transferOwnershipButton.click(); + } + + async confirmTransferOwnership() { + const transferModal = this.selector.memberSettings.transferOwnershipModal.container; + await expect(transferModal).toBeVisible({ timeout: 3000 }); + const confirmInput = this.selector.memberSettings.transferOwnershipModal.confirmTransferInput; + await confirmInput.click(); + const confirmButton = this.selector.memberSettings.transferOwnershipModal.confirmTransferButton; + await confirmButton.click(); + } + + async verifyOwnerIconIsVisibleInMemberList(memberItem: Locator) { + const ownerIconLocator = memberItem.locator(this.selector.secondarySideBar.member.ownerIcon); + await expect(ownerIconLocator).toBeVisible({ timeout: 3000 }); + } + + async verifyOwnerCannotLeaveClan() { + await this.selector.buttons.clanName.click(); + const leaveClanButtonVisible = await this.selector.buttons.leaveClan.isVisible({ + timeout: 3000, + }); + expect(leaveClanButtonVisible).toBeFalsy(); + } + + async verifyOwnerCanDeleteClan() { + await this.selector.buttons.clanSettings.click(); + + const deleteClanButton = this.selector.clanSettings.buttons.deleteClan; + await expect(deleteClanButton).toBeVisible({ timeout: 3000 }); + } + + async verifyOwnerCanKickMembers(username: string) { + const memberLocator = this.selector.sidebarMemberList.memberItems + .filter({ hasText: username }) + .first(); + await expect(memberLocator).toBeVisible({ timeout: 3000 }); + await memberLocator.click({ button: 'right' }); + const kickButtonVisible = await this.selector.sidebarMemberList.kickButton.isVisible({ + timeout: 3000, + }); + expect(kickButtonVisible).toBeTruthy(); + } + + async joinVoiceChannelFromMessage(channelName: string): Promise { + const messageSelector = new MessageSelector(this.page); + const voiceChannelMessage = messageSelector.messages + .filter({ + hasText: channelName, + }) + .last(); + await voiceChannelMessage.click(); + const joinButtonLocator = this.selector.screen.voiceRoom.joinButton; + try { + await joinButtonLocator.waitFor({ state: 'visible', timeout: 5000 }); + await joinButtonLocator.click(); + return true; + } catch { + return false; + } + } + + async clickWaveButton(username: string) { + const messageSelector = new MessageSelector(this.page); + + const targetMessage = messageSelector.systemMessages.filter({ + has: messageSelector.mentionUser.filter({ hasText: username }), + }); + + await targetMessage.locator(messageSelector.waveToSayHiButton).first().click(); + } + + async verifyWelcomeMessageInChannel() { + const messageSelector = new MessageSelector(this.page); + + const lastMessage = messageSelector.messages.last(); + await expect(lastMessage).toBeVisible({ timeout: 3000 }); + + const gifMessage = lastMessage.locator('[id*=".gif"]'); + + await expect(gifMessage).toBeVisible(); + } } diff --git a/src/pages/FriendPage.ts b/src/pages/FriendPage.ts index f5314a9c..d457cf3f 100644 --- a/src/pages/FriendPage.ts +++ b/src/pages/FriendPage.ts @@ -337,4 +337,8 @@ export class FriendPage extends BasePage { .filter({ hasText: status }); await expect(statusLocator).toBeVisible({ timeout: 5000 }); } + + async getDMByUsername(username: string) { + return this.selector.dm.items.filter({ hasText: username }).first(); + } } diff --git a/src/pages/HomePage.ts b/src/pages/HomePage.ts index b4c62c05..38bdd89c 100644 --- a/src/pages/HomePage.ts +++ b/src/pages/HomePage.ts @@ -1,7 +1,7 @@ +import HomePageSelector from '@/data/selectors/HomePageSelector'; import { type Page, expect } from '@playwright/test'; import { WEBSITE_CONFIGS } from '../config/environment'; import { BasePage } from './BasePage'; -import HomePageSelector from '@/data/selectors/HomePageSelector'; export class HomePage extends BasePage { private selector: HomePageSelector; diff --git a/src/pages/MessagePage.ts b/src/pages/MessagePage.ts index cc942f91..fec3ccd4 100644 --- a/src/pages/MessagePage.ts +++ b/src/pages/MessagePage.ts @@ -804,6 +804,82 @@ export class MessagePage extends BasePage { async verifyShortProfileIsUnknownUser() { expect(this.selector.shortProfile.displayName).toBeHidden({ timeout: 2000 }); expect(this.selector.shortProfile.username).toBeHidden({ timeout: 2000 }); - await expect(this.selector.shortProfile.avatar).toBeVisible({ timeout: 2000 }); + await expect(this.selector.anonymous.anonymousAvatar).toBeVisible({ timeout: 2000 }); + } + + async sendMessageWithAnonymous(message: string): Promise { + try { + await this.page.keyboard.press('Control+Shift+Enter'); + await this.page.waitForTimeout(3000); + + await this.selector.anonymous.anonymousIcon.waitFor({ state: 'visible', timeout: 5000 }); + const isAnonymousIconVisible = await this.selector.anonymous.anonymousIcon.isVisible(); + if (!isAnonymousIconVisible) { + throw new Error('Anonymous icon is not visible after enabling anonymous mode'); + } + + await this.selector.messageInput.click(); + await this.selector.messageInput.fill(message); + await this.selector.messageInput.press('Enter'); + await this.page.waitForLoadState('networkidle'); + + this.message = message; + } catch (error) { + console.error('Error sending anonymous message:', error); + throw error; + } + } + + async isAnonymousIconVisible(): Promise { + try { + await this.selector.anonymous.anonymousIcon.waitFor({ state: 'visible', timeout: 5000 }); + return true; + } catch { + return false; + } + } + + async isAnonymousMessageSent(): Promise { + try { + const messageLocator = this.page.locator(`text="${this.message}"`); + await messageLocator.waitFor({ state: 'visible', timeout: 5000 }); + + await this.selector.anonymous.anonymousMessage.waitFor({ state: 'visible', timeout: 5000 }); + + await this.selector.anonymous.anonymousName.waitFor({ state: 'visible', timeout: 5000 }); + + return true; + } catch (error) { + console.error('Error verifying anonymous message:', error); + return false; + } + } + + async unpinLastMessage() { + const lastMessage = this.selector.messages.last(); + await expect(lastMessage).toBeVisible({ timeout: 5000 }); + await lastMessage.click({ button: 'right' }); + await this.page.waitForTimeout(1000); + await this.selector.unpinMessageButton.click(); + await this.page.waitForTimeout(1000); + } + + async verifyMessageIsUnpinned(message: string): Promise { + await this.selector.displayListPinButton.click(); + const pinnedMessage = this.selector.pinnedMessages.filter({ hasText: message }); + return (await pinnedMessage.count()) === 0; + } + + async markMessageAsUnread(username: string) { + const lastMessage = this.selector.messages.filter({ hasText: username }).last(); + await expect(lastMessage).toBeVisible({ timeout: 5000 }); + await lastMessage.click({ button: 'right' }); + await this.page.waitForTimeout(1000); + await this.selector.markAsUnreadButton.click(); + await this.page.waitForTimeout(1000); + } + + async getHeaderDM() { + return this.selector.headerDM.first(); } } diff --git a/src/pages/OnboardingPage.ts b/src/pages/OnboardingPage.ts index 0113c887..51a4d6af 100644 --- a/src/pages/OnboardingPage.ts +++ b/src/pages/OnboardingPage.ts @@ -1,9 +1,15 @@ -import { type Page } from '@playwright/test'; -import { BasePage } from './BasePage'; -import { generateE2eSelector } from '@/utils/generateE2eSelector'; +import ClanSelector from '@/data/selectors/ClanSelector'; import type { OnboardingTaskType } from '@/types/onboarding.types'; +import { generateE2eSelector } from '@/utils/generateE2eSelector'; +import { expect, type Page } from '@playwright/test'; +import { BasePage } from './BasePage'; export class OnboardingPage extends BasePage { + private readonly selector: ClanSelector; + constructor(page: Page, baseURL?: string) { + super(page, baseURL); + this.selector = new ClanSelector(page); + } private readonly onboardingGuideSelectors = [ '[data-testid="onboarding-guide"]', '.onboarding-guide', @@ -30,10 +36,6 @@ export class OnboardingPage extends BasePage { 'div.bg-green-600', ]; - constructor(page: Page, baseURL?: string) { - super(page, baseURL); - } - async openOnboardingGuide(): Promise { for (const selector of this.onboardingGuideSelectors) { try { @@ -109,4 +111,116 @@ export class OnboardingPage extends BasePage { return false; } + + async verifyEnableOnboardingOnClanSettingsSidebar(shouldEnable = true) { + const onboardingStatus = this.selector.onboarding.status; + const buttonDisable = this.selector.onboarding.buttons.disableOnboarding; + if (shouldEnable) { + await expect(onboardingStatus).toHaveText('ON'); + await expect(buttonDisable).toBeVisible({ timeout: 3000 }); + } else { + await expect(onboardingStatus).toHaveText('OFF'); + await expect(buttonDisable).toBeHidden({ timeout: 3000 }); + } + } + + async openOnboardingTab() { + const onboardingSidebar = this.selector.clanSettings.buttons.sidebarItem.filter({ + hasText: 'Onboarding', + }); + await onboardingSidebar.click(); + } + + async clickEnableOnboarding() { + await this.selector.onboarding.buttons.enableOnboarding.click(); + } + + async addPrequestionOnboaring(question: string, answerTitle: string, answerDescription: string) { + await this.selector.onboarding.setupQuestion.item.click(); + await this.selector.onboarding.setupQuestion.button.addQuestion.click(); + this.page.waitForTimeout(500); + await this.selector.onboarding.setupQuestion.button.questionItem.first().click(); + this.page.waitForTimeout(500); + await this.selector.onboarding.setupQuestion.input.question.fill(question); + await this.selector.onboarding.setupQuestion.button.addAnswer.click(); + this.page.waitForTimeout(500); + await this.selector.onboarding.setupQuestion.input.answerTitle.fill(answerTitle); + await this.selector.onboarding.setupQuestion.input.answerDescription.fill(answerDescription); + await this.selector.onboarding.setupQuestion.button.confirmAnswer.click(); + await this.selector.onboarding.setupQuestion.button.saveQuestion.click(); + await this.selector.onboarding.setupQuestion.button.saveAll.click(); + } + + async addTaskOnboarding(taskName: string) { + await this.selector.onboarding.clanGuideSettings.item.click(); + await this.selector.onboarding.clanGuideSettings.buttons.addTask.click(); + await this.selector.onboarding.clanGuideSettings.input.taskTitle.fill(taskName); + await this.selector.onboarding.setupQuestion.button.confirmAnswer.click(); + } + + async clickBackOnboardingModal() { + await this.selector.onboarding.buttons.back.click(); + } + + async verifyOnboardingPageVisible(shouldVisible = true) { + const clanGuideSidebar = this.selector.onboarding.clanGuidePage.sidebar; + if (shouldVisible) { + await expect(clanGuideSidebar).toBeVisible({ timeout: 3000 }); + } else { + await expect(clanGuideSidebar).toBeHidden({ timeout: 3000 }); + } + } + + async openOnboardingPage() { + const clanGuideSidebar = this.selector.onboarding.clanGuidePage.sidebar; + await clanGuideSidebar.click(); + } + + async verifyOnboardingSetupByType( + type: 'question' | 'resource' | 'mission', + title: string, + description?: string, + question?: string + ) { + const { + question: questionLocator, + title: titleLocator, + description: descriptionLocator, + } = this.selector.onboarding.clanGuidePage; + + switch (type) { + case 'question': { + if (!question) return; + + await expect(questionLocator).toHaveText(question); + await expect(titleLocator).toHaveText(title); + + if (description) { + await expect(descriptionLocator).toHaveText(description); + } + break; + } + + case 'resource': + case 'mission': { + await expect(titleLocator).toHaveText(title); + + if (description) { + await expect(descriptionLocator).toHaveText(description); + } + break; + } + + default: + throw new Error(`Unsupported onboarding type: ${type}`); + } + } + + async openOnboardingPreviewMode() { + await this.selector.onboarding.buttons.openPreviewMode.click(); + } + + async closeOnboardingPreviewMode() { + await this.selector.onboarding.buttons.closePreviewMode.click(); + } } diff --git a/src/pages/ProfilePage.ts b/src/pages/ProfilePage.ts index 2f43ab89..b716027b 100644 --- a/src/pages/ProfilePage.ts +++ b/src/pages/ProfilePage.ts @@ -1,7 +1,7 @@ import MessageSelector from '@/data/selectors/MessageSelector'; import ProfileSelector from '@/data/selectors/ProfileSelector'; import { generateE2eSelector } from '@/utils/generateE2eSelector'; -import { expect, Page } from '@playwright/test'; +import { expect, Locator, Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { MessagePage } from './MessagePage'; @@ -196,4 +196,89 @@ export class ProfilePage extends BasePage { async closeSettingsProfile() { await this.selector.buttons.closeSettingProfile.click(); } + + async getProfileStatusInFooterProfile() { + const locator = this.selector.shortProfile.profileStatus.triggerButton; + + if (await locator.locator('.bg-green-500').isVisible()) { + return 'online'; + } + + if (await locator.locator('.bg-red-500').isVisible()) { + return 'Do Not Disturb'; + } + + if (await locator.locator('svg.text-\\[\\#F0B232\\]').isVisible()) { + return 'Idle'; + } + + if (await locator.locator('rect[stroke="#AEAEAE"]').isVisible()) { + return 'Invisible'; + } + + return 'Unknown'; + } + + async openSelectProfileStatusModal(currentStatus: string) { + const buttonTrigger = this.selector.shortProfile.profileStatus.triggerButton.locator('li', { + hasText: currentStatus, + }); + await expect(buttonTrigger).toBeVisible({ timeout: 3000 }); + await buttonTrigger.click(); + } + + async getProfileStatus(locator: Locator): Promise { + if (await locator.locator('.bg-green-500').isVisible()) { + return 'online'; + } + + if (await locator.locator('.bg-red-500').isVisible()) { + return 'Do Not Disturb'; + } + + if (await locator.locator('svg.text-\\[\\#F0B232\\]').isVisible()) { + return 'Idle'; + } + + if (await locator.locator('rect[stroke="#AEAEAE"]').isVisible()) { + return 'Invisible'; + } + + return 'Unknown'; + } + + async setProfileStatus(newStatus: string, timeVisible?: string) { + const statusLocator = this.selector.shortProfile.profileStatus.triggerButton + .filter({ hasText: newStatus }) + .first(); + await expect(statusLocator).toBeVisible({ timeout: 3000 }); + await statusLocator.click(); + const timeLocator = this.selector.shortProfile.profileStatus.triggerButton + .filter({ hasText: timeVisible }) + .first(); + if (timeVisible) { + await timeLocator.click(); + } + } + + async verifyNewStatusVisibleShortProfile(expectedStatus: string) { + const previewStatus = this.selector.shortProfile.modal.container.locator( + this.selector.shortProfile.profileStatus.status + ); + + const actualStatus = await this.getProfileStatus(previewStatus); + + expect(actualStatus).toBe(expectedStatus); + } + + async verifyNewProfileStatusVisibleDueToLocator(userLocator: Locator, expectedStatus: string) { + const profileStatus = userLocator.locator(this.selector.shortProfile.profileStatus.status); + const actualStatus = await this.getProfileStatus(profileStatus); + + expect(actualStatus).toBe(expectedStatus); + } + + async getUserProfileLocator() { + return this.selector.shortProfile.modal.container.first(); + } } diff --git a/src/selectors/index.ts b/src/selectors/index.ts index b58a7d14..98777c87 100644 --- a/src/selectors/index.ts +++ b/src/selectors/index.ts @@ -30,6 +30,9 @@ export const DATA_E2E_IDENTIFIER = { base_profile: { display_name: '', user_status: '', + anonymous: { + avatar: '', + }, }, short_profile: { display_name: '', @@ -178,6 +181,7 @@ export const DATA_E2E_IDENTIFIER = { events: '', members: '', channels: '', + clan_guide: '', }, }, member_list: { @@ -195,6 +199,12 @@ export const DATA_E2E_IDENTIFIER = { role_name: '', }, }, + actions: '', + transfer_owner_modal: { + input: { + confirm_transfer: '', + }, + }, }, modal: { create_category: { @@ -346,12 +356,36 @@ export const DATA_E2E_IDENTIFIER = { title: '', delete: '', item: '', + onboarding_status: '', }, onboarding: { button: { enable_onboarding: '', add_resources: '', clan_guide: '', + setup_question: '', + add_question: '', + remove_question: '', + save_change: '', + save_all: '', + add_task: '', + cancel: '', + back: '', + disable_onboarding: '', + open_preview_mode: '', + close_preview_mode: '', + }, + input: { + question: '', + answer: '', + }, + question: { + item: '', + }, + clan_guide: { + input: { + title: '', + }, }, }, community: { @@ -392,6 +426,7 @@ export const DATA_E2E_IDENTIFIER = { }, }, }, + prevent_anonymous: '', }, upload: { clan_logo_input: '', @@ -582,7 +617,9 @@ export const DATA_E2E_IDENTIFIER = { }, direct_message: { header: { - left_container: '', + left_container: { + in_voice_status: '', + }, right_container: { user_profile: '', call: '', @@ -598,6 +635,7 @@ export const DATA_E2E_IDENTIFIER = { close_dm_button: '', text_area: '', namegroup: '', + in_voice_status: '', }, message_buzz: { header: '', @@ -777,11 +815,30 @@ export const DATA_E2E_IDENTIFIER = { }, number_replies: '', }, + anonymous: '', + share_contact: { + display_name: '', + username: '', + button: { + call: '', + message: '', + }, + }, + button: { + wave_to_say_hi: '', + }, }, onboarding: { chat: { guide_sections: '', }, + clan_guide_page: { + label: '', + question: '', + title: '', + description: '', + action: '', + }, }, user_setting: { account: { @@ -874,6 +931,15 @@ export const DATA_E2E_IDENTIFIER = { search: { input: '', }, + share_contact: { + input: { + search: '', + }, + button: { + cancel: '', + share: '', + }, + }, }, friend_page: { tab: { @@ -902,6 +968,8 @@ export const DATA_E2E_IDENTIFIER = { }, icon: { end_call: '', + owner: '', + profile_status: '', }, message: { item: '', diff --git a/src/tests/multi-chat/ChannelMessage/ChannelMessage3.spec.ts b/src/tests/multi-chat/ChannelMessage/ChannelMessage3.spec.ts index 21aa8be0..6b98b960 100644 --- a/src/tests/multi-chat/ChannelMessage/ChannelMessage3.spec.ts +++ b/src/tests/multi-chat/ChannelMessage/ChannelMessage3.spec.ts @@ -226,4 +226,199 @@ test.describe('Channel Message 3', () => { expect(lastMessage).toContain(testMessage); }); }); + + test('Verify that user can share friend contact card in clan channel', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64609', + }); + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can share friend contact card in clan channel + Steps: + 1. User A create clan + 2. User A invite user B + 3. User B accept invite + 4. User A share friend contact card in clan channel + **Expected Result:** User can share friend contact card in clan channel + `); + + await AllureReporter.addLabels({ + tag: ['clan', 'contact-card', 'share'], + }); + const clanPageA = new ClanPage(pageA); + const clanPageB = new ClanPage(pageB); + const messageHelperA = new MessageTestHelpers(pageA); + const messageHelperB = new MessageTestHelpers(pageB); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + }); + await AllureReporter.step('User A invite user B to clan and user B accept it', async () => { + await pageA.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageA.clickButtonInvitePeopleFromMenu(); + const url = await clanPageA.inviteUserToClanByUsername(userNameB); + await clanPageB.joinClanByUrlInvite(url); + }); + + await AllureReporter.step('User A share friend contact card in clan channel', async () => { + await clanPageA.openMemberList(); + await clanPageA.clickShareContactByName(userNameB); + await messageHelperA.verifyShareContactModalVisible(); + await messageHelperA.shareContactInDMOrChannel('general'); + }); + await AllureReporter.step('Verify contact card message is visible on destination', async () => { + await pageB.reload(); + await pageB.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await messageHelperB.verifyContactSharedInDMOrChannel(userNameB); + }); + }); + + test('Verify that user can not share friend contact card to friend`s DM', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64609', + }); + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can not share friend contact card in friend's DM + Steps: + 1. User A create clan + 2. User A invite user B + 3. User B accept invite + 4. User A share friend contact card to friend's DM + **Expected Result:** User can not share friend contact card in friend's DM + `); + + await AllureReporter.addLabels({ + tag: ['clan', 'contact-card', 'share', 'dm'], + }); + const clanPageA = new ClanPage(pageA); + const clanPageB = new ClanPage(pageB); + const messageHelperA = new MessageTestHelpers(pageA); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + }); + await AllureReporter.step('User A invite user B to clan and user B accept it', async () => { + await pageA.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageA.clickButtonInvitePeopleFromMenu(); + const url = await clanPageA.inviteUserToClanByUsername(userNameB); + await clanPageB.joinClanByUrlInvite(url); + }); + + await AllureReporter.step('User A click share friend contact card to friend`s dm', async () => { + await clanPageA.openMemberList(); + await clanPageA.clickShareContactByName(userNameB); + await messageHelperA.verifyShareContactModalVisible(); + await messageHelperA.shareContactInDMOrChannel(userNameB, false); + }); + }); + + test('Verify that user can not call itself from share contact', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64609', + }); + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can not call itself from share contact + Steps: + 1. User A create clan + 2. User A invite user B + 3. User B accept invite + 4. User A share friend contact card to channel + 5. User B verify call item is not present on shared contact card + **Expected Result:** User can not call itself from share contact + `); + + await AllureReporter.addLabels({ + tag: ['clan', 'contact-card', 'share', 'call'], + }); + const clanPageA = new ClanPage(pageA); + const clanPageB = new ClanPage(pageB); + const messageHelperA = new MessageTestHelpers(pageA); + const messageHelperB = new MessageTestHelpers(pageB); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + }); + await AllureReporter.step('User A invite user B to clan and user B accept it', async () => { + await pageA.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageA.clickButtonInvitePeopleFromMenu(); + const url = await clanPageA.inviteUserToClanByUsername(userNameB); + await clanPageB.joinClanByUrlInvite(url); + }); + + await AllureReporter.step('User A click share friend contact card to friend`s dm', async () => { + await clanPageA.openMemberList(); + await clanPageA.clickShareContactByName(userNameB); + await messageHelperA.verifyShareContactModalVisible(); + await messageHelperA.shareContactInDMOrChannel('general'); + }); + + await AllureReporter.step('Verify contact card message has not call item', async () => { + await pageB.reload(); + await pageB.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await messageHelperB.verifyContactSharedInDMOrChannel(userNameB); + await messageHelperB.verifyCallItemVisibleInShareContactCard(userNameB, false); + }); + }); }); diff --git a/src/tests/multi-chat/ChannelMessage/ChannelMessage4.spec.ts b/src/tests/multi-chat/ChannelMessage/ChannelMessage4.spec.ts new file mode 100644 index 00000000..6ad661ca --- /dev/null +++ b/src/tests/multi-chat/ChannelMessage/ChannelMessage4.spec.ts @@ -0,0 +1,417 @@ +import { AccountCredentials, WEBSITE_CONFIGS } from '@/config/environment'; +import { ClanFactory } from '@/data/factories/ClanFactory'; +import { ClanPage } from '@/pages/Clan/ClanPage'; +import { FriendPage } from '@/pages/FriendPage'; +import { MessagePage } from '@/pages/MessagePage'; +import { ROUTES } from '@/selectors'; +import { ChannelType } from '@/types/clan-page.types'; +import { AllureReporter } from '@/utils/allureHelpers'; +import { AuthHelper } from '@/utils/authHelper'; +import { ClanSetupHelper } from '@/utils/clanSetupHelper'; +import { getUsernamesFromEmails } from '@/utils/dualTestHelper'; +import { FriendHelper } from '@/utils/friend.helper'; +import joinUrlPaths from '@/utils/joinUrlPaths'; +import { MessageTestHelpers } from '@/utils/messageHelpers'; +import TestSuiteHelper from '@/utils/testSuite.helper'; +import { expect } from '@playwright/test'; +import { test } from '../../../fixtures/dual.fixture'; + +test.describe('Channel Message 4', () => { + const accountA = AccountCredentials['accountKien2']; + const accountB = AccountCredentials['accountKien3']; + const accountC = AccountCredentials['accountKien4']; + const CLEANUP_STEP_NAME = 'Clean up existing friend relationships'; + const SEND_REQUEST_STEP_NAME = 'User A sends friend request to User B'; + const clanFactory = new ClanFactory(); + const [userNameA, userNameB, userNameC] = getUsernamesFromEmails([ + accountA.email, + accountB.email, + accountC.email, + ]); + + test.beforeAll(async ({ browser }) => { + await TestSuiteHelper.setupBeforeAll({ + browser, + clanFactory, + configs: ClanSetupHelper.configs.channelMessage1, + credentials: accountA, + }); + }); + + test.beforeEach(async ({ dual }) => { + await dual.parallel({ + A: async () => { + const credentials = await AuthHelper.setupAuthWithEmailPassword(dual.pageA, accountA); + await AuthHelper.prepareBeforeTest( + dual.pageA, + joinUrlPaths(WEBSITE_CONFIGS.MEZON.baseURL, ROUTES.DIRECT_FRIENDS), + credentials + ); + }, + B: async () => { + const credentials = await AuthHelper.setupAuthWithEmailPassword(dual.pageB, accountB); + await AuthHelper.prepareBeforeTest( + dual.pageB, + joinUrlPaths(WEBSITE_CONFIGS.MEZON.baseURL, ROUTES.DIRECT_FRIENDS), + credentials + ); + }, + }); + }); + + test.afterAll(async ({ browser }) => { + await TestSuiteHelper.onAfterAll({ + browser, + clanFactory, + credentials: accountA, + }); + }); + + test.afterEach(async ({ dual }) => { + await dual.parallel({ + A: async page => { + await AuthHelper.logout(page); + }, + B: async page => { + await AuthHelper.logout(page); + }, + }); + }); + + test('Verify that user can share contact in group', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64609', + }); + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + const messagePageA = new MessagePage(pageA); + const messagePageB = new MessagePage(pageB); + const unique = Date.now().toString(36).slice(-6); + const nameGroupChat = `groupchat-${unique}`.slice(0, 20); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can share contact in group + Steps: + 1. User A create clan + 2. User A invite user B + 3. User B accept invite + 4. User A share friend contact card on group + **Expected Result:** User can share contact in group + `); + + await AllureReporter.addLabels({ + tag: ['group', 'contact-card', 'share'], + }); + const clanPageA = new ClanPage(pageA); + const messageHelperA = new MessageTestHelpers(pageA); + const messageHelperB = new MessageTestHelpers(pageB); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await friendPageA.sendFriendRequestToUser(userNameC); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step( + 'User A create a group, send a message and update new avatar', + async () => { + await messagePageA.createGroup(); + await dual.pageA.waitForTimeout(1000); + + await messagePageA.sendMessageWhenInDM('This is a message to forward'); + await dual.pageA.waitForTimeout(1000); + + await messagePageA.updateNameGroupChatDM(nameGroupChat); + await dual.pageA.waitForTimeout(1000); + + await messagePageA.openGroupFromName(nameGroupChat); + } + ); + + await AllureReporter.step('User A click share friend contact card to group', async () => { + await messagePageA.showMemberGroup(); + await clanPageA.clickShareContactByName(userNameB); + await messageHelperA.verifyShareContactModalVisible(); + await messageHelperA.shareContactInDMOrChannel(nameGroupChat); + }); + + await AllureReporter.step('Verify contact card message is visible on destination', async () => { + await pageB.reload(); + await messagePageB.openGroupFromName(nameGroupChat); + await messageHelperB.verifyContactSharedInDMOrChannel(userNameB); + await messageHelperB.verifyCallItemVisibleInShareContactCard(userNameB, false); + }); + }); + + test('Verify that user can share contact and when click button message on card, it will navigate to friend`s dm', async ({ + dual, + }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64609', + }); + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + const messagePageA = new MessagePage(pageA); + const messagePageB = new MessagePage(pageB); + const unique = Date.now().toString(36).slice(-6); + const nameGroupChat = `groupchat-${unique}`.slice(0, 20); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can share contact and when click button message on card, it will navigate to friend's dm + Steps: + 1. User A create clan + 2. User A invite user B + 3. User B accept invite + 4. User A share friend contact card on group + **Expected Result:** User can share contact and when click button message on card, it will navigate to friend's dm + `); + + await AllureReporter.addLabels({ + tag: ['group', 'contact-card', 'share', 'message-button'], + }); + const clanPageA = new ClanPage(pageA); + const messageHelperA = new MessageTestHelpers(pageA); + const messageHelperB = new MessageTestHelpers(pageB); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await friendPageA.sendFriendRequestToUser(userNameC); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step( + 'User A create a group, send a message and update new avatar', + async () => { + await messagePageA.createGroup(); + await dual.pageA.waitForTimeout(1000); + + await messagePageA.sendMessageWhenInDM('This is a message to forward'); + await dual.pageA.waitForTimeout(1000); + + await messagePageA.updateNameGroupChatDM(nameGroupChat); + await dual.pageA.waitForTimeout(1000); + + await messagePageA.openGroupFromName(nameGroupChat); + } + ); + + await AllureReporter.step('User A click share friend contact card to group', async () => { + await messagePageA.showMemberGroup(); + await clanPageA.clickShareContactByName(userNameB); + await messageHelperA.verifyShareContactModalVisible(); + await messageHelperA.shareContactInDMOrChannel(nameGroupChat); + }); + + await AllureReporter.step('Click message on share contact card', async () => { + await pageB.reload(); + await messagePageB.openGroupFromName(nameGroupChat); + await messageHelperB.verifyContactSharedInDMOrChannel(userNameB); + await messageHelperB.clickMessageOnShareContactCard(); + }); + + await AllureReporter.step("Verify navigate to friend's dm", async () => { + const usernameLocator = await messagePageB.getGroupName(); + await expect(usernameLocator).toHaveText(userNameB); + }); + }); + + test('Verify that user can join voice channel from link', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64645', + github_issue: '9816', + }); + + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can join voice channel from link + + **Test Steps:** + 1. Create new voice channel + 2. Join voice channel + 3. Copy voice channel link + 4. Send voice channel link in general channel + 4. Verify voice channel link is sent + + **Expected Result:** Voice channel link is sent + `); + + await AllureReporter.addLabels({ + tag: ['voice-channel', 'send-voice-channel-link'], + }); + const clanPageA = new ClanPage(pageA); + const clanPageB = new ClanPage(pageB); + const messageHelperA = new MessageTestHelpers(pageA); + const messageHelperB = new MessageTestHelpers(pageB); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await friendPageA.sendFriendRequestToUser(userNameC); + await friendPageA.verifySentRequestToast(); + }); + + const ran = Math.floor(Math.random() * 999) + 1; + const channelName = `voice-channel-${ran}`; + + await AllureReporter.step(`Create new voice channel: ${channelName}`, async () => { + await clanPageA.createNewChannel(ChannelType.VOICE, channelName); + const isNewChannelPresent = await clanPageA.isNewChannelPresent(channelName); + expect(isNewChannelPresent).toBe(true); + }); + + await AllureReporter.step('Join voice channel', async () => { + await clanPageA.joinVoiceChannel(channelName); + const isUserInVoiceChannel = await clanPageA.isJoinVoiceChannel(channelName); + expect(isUserInVoiceChannel).toBe(true); + }); + + await AllureReporter.step('Copy and send voice channel link', async () => { + await clanPageA.copyVoiceChannelLink(); + await clanPageA.openChannelByName('general'); + await messageHelperA.pasteAndSendText(); + await pageA.waitForTimeout(3000); + }); + + await AllureReporter.step('Verify voice channel link is sent', async () => { + const isVoiceChannelLinkSent = await messageHelperA.verifyLastMessageHasText(channelName); + expect(isVoiceChannelLinkSent).toBe(true); + }); + + await AllureReporter.step('Verify voice channel link is visible to User B', async () => { + await pageB.reload(); + await clanPageB.openChannelByName('general'); + const isVoiceChannelLinkSent = await messageHelperB.verifyLastMessageHasText(channelName); + expect(isVoiceChannelLinkSent).toBe(true); + }); + + await AllureReporter.step( + 'User B clicks on voice channel link and joins the channel', + async () => { + await clanPageB.joinVoiceChannelFromMessage(channelName); + const isUserInVoiceChannel = await clanPageB.isJoinVoiceChannel(channelName); + expect(isUserInVoiceChannel).toBe(true); + } + ); + }); + + test('Verify that user can click wave to save hi and welcome message is visible', async ({ + dual, + }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64645', + github_issue: '9816', + }); + + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can click wave to save hi and welcome message is visible + + **Test Steps:** + 1. Invite friend to clan + 2. Friend accepts invite + 3. Clicks wave to say hi + 4. Verify welcome message is visible + `); + + await AllureReporter.addLabels({ + tag: ['channel', 'wave', 'welcome-message'], + }); + const clanPageA = new ClanPage(pageA); + const clanPageB = new ClanPage(pageB); + + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + }); + + await AllureReporter.step('User A invite user B to clan and user B accept it', async () => { + await pageA.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageA.clickButtonInvitePeopleFromMenu(); + const url = await clanPageA.inviteUserToClanByUsername(userNameB); + await clanPageB.joinClanByUrlInvite(url); + }); + + await AllureReporter.step('User A clicks wave to say hi to User B', async () => { + await clanPageA.clickWaveButton(userNameB); + }); + + await AllureReporter.step('Verify welcome message is visible to User B', async () => { + const isWelcomeMessageVisible = await clanPageA.verifyWelcomeMessageInChannel(); + expect(isWelcomeMessageVisible).toBe(true); + }); + }); +}); diff --git a/src/tests/multi-chat/ClanManagement/ClanManagement2.spec.ts b/src/tests/multi-chat/ClanManagement/ClanManagement2.spec.ts index 0fe15e88..2aaba2b1 100644 --- a/src/tests/multi-chat/ClanManagement/ClanManagement2.spec.ts +++ b/src/tests/multi-chat/ClanManagement/ClanManagement2.spec.ts @@ -240,4 +240,131 @@ test.describe('Clan Management 2', () => { } ); }); + + test('Verify that user can transfer owenship to another member', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64610', + }); + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can transfer owenship to another member. + Steps: + 1. User A create clan + 2. User A invite user B + 3. User B accept invite + 4. User A transfer ownership to user B + `); + const clanPageA = new ClanPage(pageA); + const clanPageB = new ClanPage(pageB); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + }); + await AllureReporter.step('User A invite user B to clan and user B accept it', async () => { + await pageA.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageA.clickButtonInvitePeopleFromMenu(); + const url = await clanPageA.inviteUserToClanByUsername(userNameB); + await clanPageB.joinClanByUrlInvite(url); + }); + await AllureReporter.step('User A transfer ownership to user B', async () => { + await clanPageA.openMemberListSetting(); + await clanPageA.openMemberActionsMenu(userNameB); + await clanPageA.clickTransferClanOwnershipButton(); + await clanPageA.confirmTransferOwnership(); + }); + + await AllureReporter.step('Verify user B is the owner of clan', async () => { + await pageB.reload(); + await pageB.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageB.openMemberList(); + const memberItem = await clanPageB.getMemberFromMemberList(userNameB); + await clanPageB.verifyOwnerIconIsVisibleInMemberList(memberItem); + }); + }); + + test('Verify that the person to whom ownership is transferred has full admin privileges.', async ({ + dual, + }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64610', + }); + const { pageA, pageB } = dual; + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + await AllureReporter.addDescription(` + **Test Objective:** Verify that the person to whom ownership is transferred has full admin privileges. + Steps: + 1. User A create clan + 2. User A invite user B + 3. User B accept invite + 4. User A transfer ownership to user B + `); + const clanPageA = new ClanPage(pageA); + const clanPageB = new ClanPage(pageB); + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + }); + await AllureReporter.step('User A invite user B to clan and user B accept it', async () => { + await pageA.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageA.clickButtonInvitePeopleFromMenu(); + const url = await clanPageA.inviteUserToClanByUsername(userNameB); + await clanPageB.joinClanByUrlInvite(url); + }); + await AllureReporter.step('User A transfer ownership to user B', async () => { + await clanPageA.openMemberListSetting(); + await clanPageA.openMemberActionsMenu(userNameB); + await clanPageA.clickTransferClanOwnershipButton(); + await clanPageA.confirmTransferOwnership(); + }); + + await AllureReporter.step('Verify user B is has full admin privileges', async () => { + await pageB.reload(); + await pageB.goto(clanFactory.getClanUrl(), { waitUntil: 'domcontentloaded' }); + await clanPageB.openMemberList(); + const memberItem = await clanPageB.getMemberFromMemberList(userNameB); + await clanPageB.verifyOwnerIconIsVisibleInMemberList(memberItem); + await clanPageB.verifyOwnerCanKickMembers(userNameA); + await clanPageB.verifyOwnerCannotLeaveClan(); + await clanPageB.verifyOwnerCanDeleteClan(); + }); + }); }); diff --git a/src/tests/multi-chat/DirectMessage.spec.ts b/src/tests/multi-chat/DirectMessage/DirectMessage.spec.ts similarity index 85% rename from src/tests/multi-chat/DirectMessage.spec.ts rename to src/tests/multi-chat/DirectMessage/DirectMessage.spec.ts index e338beb5..6423e070 100644 --- a/src/tests/multi-chat/DirectMessage.spec.ts +++ b/src/tests/multi-chat/DirectMessage/DirectMessage.spec.ts @@ -4,11 +4,12 @@ import { MessagePage } from '@/pages/MessagePage'; import { ROUTES } from '@/selectors'; import { AllureReporter } from '@/utils/allureHelpers'; import { AuthHelper } from '@/utils/authHelper'; +import { getUsernamesFromEmails } from '@/utils/dualTestHelper'; import { FriendHelper } from '@/utils/friend.helper'; import joinUrlPaths from '@/utils/joinUrlPaths'; +import { MessageTestHelpers } from '@/utils/messageHelpers'; import { expect } from '@playwright/test'; -import { test } from '../../fixtures/dual.fixture'; -import { getUsernamesFromEmails } from '@/utils/dualTestHelper'; +import { test } from '../../../fixtures/dual.fixture'; test.describe('Direct Message', () => { const accountA = AccountCredentials['account2-3']; @@ -412,4 +413,62 @@ test.describe('Direct Message', () => { } ); }); + + test('Verify that user can mark as unread on DM', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64802', + github_issue: '10161', + }); + const { pageA, pageB } = dual; + const messagePageA = new MessagePage(pageA); + const messagePageB = new MessagePage(pageB); + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + const messageHelperB = new MessageTestHelpers(pageB); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can mark as unread on DM + **Test Steps:** + 1. Clean up any existing friend relationships between users + 2. User A sends a friend request to User B + 3. User B receives and accepts the friend request + 4. Verify both users see each other in their friends list + 5. User A create a DM with User B + 6. User A send message to User B + 7. User B mark the DM as unread + **Expected Result:** User can mark as unread on DM + `); + + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await friendPageA.sendFriendRequestToUser(userNameC); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User A create a DM with User B and send message', async () => { + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + await messagePageA.sendMessageWhenInDM('Hello from User A to User B'); + }); + await AllureReporter.step('User B mark the message as unread', async () => { + await messagePageB.markMessageAsUnread(userNameA); + await friendPageB.createDM(userNameC); + await messageHelperB.verifyUserOnDMHasHighlight(userNameA); + }); + }); }); diff --git a/src/tests/multi-chat/DirectMessage/DirectMessage2.spec.ts b/src/tests/multi-chat/DirectMessage/DirectMessage2.spec.ts new file mode 100644 index 00000000..a85c1784 --- /dev/null +++ b/src/tests/multi-chat/DirectMessage/DirectMessage2.spec.ts @@ -0,0 +1,304 @@ +import { AccountCredentials, WEBSITE_CONFIGS } from '@/config/environment'; +import { FriendPage } from '@/pages/FriendPage'; +import { MessagePage } from '@/pages/MessagePage'; +import { ProfilePage } from '@/pages/ProfilePage'; +import { ROUTES } from '@/selectors'; +import { AllureReporter } from '@/utils/allureHelpers'; +import { AuthHelper } from '@/utils/authHelper'; +import { getUsernamesFromEmails } from '@/utils/dualTestHelper'; +import { FriendHelper } from '@/utils/friend.helper'; +import joinUrlPaths from '@/utils/joinUrlPaths'; +import { MessageTestHelpers } from '@/utils/messageHelpers'; +import { expect } from '@playwright/test'; +import { test } from '../../../fixtures/dual.fixture'; + +test.describe('Direct Message', () => { + const accountA = AccountCredentials['account2-3']; + const accountB = AccountCredentials['account2-4']; + const accountC = AccountCredentials['account2-5']; + const CLEANUP_STEP_NAME = 'Clean up existing friend relationships'; + const SEND_REQUEST_STEP_NAME = 'User A sends friend request to User B'; + let nameGroupChat: string; + const [userNameA, userNameB, userNameC] = getUsernamesFromEmails([ + accountA.email, + accountB.email, + accountC.email, + ]); + + test.beforeEach(async ({ dual }) => { + await dual.parallel({ + A: async () => { + const credentials = await AuthHelper.setupAuthWithEmailPassword(dual.pageA, accountA); + await AuthHelper.prepareBeforeTest( + dual.pageA, + joinUrlPaths(WEBSITE_CONFIGS.MEZON.baseURL, ROUTES.DIRECT_FRIENDS), + credentials + ); + }, + B: async () => { + const credentials = await AuthHelper.setupAuthWithEmailPassword(dual.pageB, accountB); + await AuthHelper.prepareBeforeTest( + dual.pageB, + joinUrlPaths(WEBSITE_CONFIGS.MEZON.baseURL, ROUTES.DIRECT_FRIENDS), + credentials + ); + }, + }); + }); + + test.afterEach(async ({ dual }) => { + // const { pageA, pageB } = dual; + // const messagePageA = new MessagePage(pageA); + // const messagePageB = new MessagePage(pageB); + await dual.parallel({ + A: async page => { + // await messagePageA.leaveGroupByName(nameGroupChat); + await AuthHelper.logout(page); + }, + B: async page => { + // await messagePageB.leaveGroupByName(nameGroupChat); + await AuthHelper.logout(page); + }, + }); + }); + + test('Verify that user can mark as unread on DM', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64802', + github_issue: '10161', + }); + const { pageA, pageB } = dual; + const messagePageA = new MessagePage(pageA); + const messagePageB = new MessagePage(pageB); + const friendPageA = new FriendPage(pageA); + const friendPageB = new FriendPage(pageB); + const messageHelperB = new MessageTestHelpers(pageB); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can mark as unread on DM + **Test Steps:** + 1. Clean up any existing friend relationships between users + 2. User A sends a friend request to User B + 3. User B receives and accepts the friend request + 4. Verify both users see each other in their friends list + 5. User A create a DM with User B + 6. User A send message to User B + 7. User B mark the DM as unread + **Expected Result:** User can mark as unread on DM + `); + + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + }); + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + await friendPageA.sendFriendRequestToUser(userNameC); + await friendPageA.verifySentRequestToast(); + }); + await AllureReporter.step('User A create a DM with User B and send message', async () => { + await Promise.all([friendPageA.createDM(userNameB), friendPageB.createDM(userNameA)]); + await messagePageA.sendMessageWhenInDM('Hello from User A to User B'); + }); + await AllureReporter.step('User B mark the message as unread', async () => { + await messagePageB.markMessageAsUnread(userNameA); + await friendPageB.createDM(userNameC); + await messageHelperB.verifyUserOnDMHasHighlight(userNameA); + }); + }); + + test('Verify that user can set profile status and verify it is visible on short profile', async ({ + dual, + }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64803', + github_issue: '10162', + }); + const { pageA } = dual; + const profilePage = new ProfilePage(pageA); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can set profile status and verify it is visible on short profile + **Test Steps:** + 1. User A set profile status to 'Idle' for '30 Minutes' + 2. Verify that new profile status is visible on User A's short profile + **Expected Result:** User can set profile status and verify it is visible on short profile + `); + + await AllureReporter.step("User A set profile status to 'Idle' for '30 Minutes'", async () => { + const currentProfileStatus = await profilePage.getProfileStatusInFooterProfile(); + await profilePage.openFooterProfileModal(); + await profilePage.openSelectProfileStatusModal(currentProfileStatus); + + await profilePage.setProfileStatus('Do Not Disturb', 'For 30 Minutes'); + }); + await AllureReporter.step('verify it is visible on short profile', async () => { + const currentProfileStatus = await profilePage.getProfileStatusInFooterProfile(); + expect(currentProfileStatus).toContain('Do Not Disturb'); + await profilePage.openFooterProfileModal(); + await profilePage.verifyNewStatusVisibleShortProfile(currentProfileStatus); + }); + }); + + test('Verify that profile status reflect correct on DM list, friend list', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64803', + github_issue: '10162', + }); + const { pageA, pageB } = dual; + const profilePageA = new ProfilePage(pageA); + const profilePageB = new ProfilePage(pageB); + const friendPageB = new FriendPage(pageB); + const friendPageA = new FriendPage(pageA); + await AllureReporter.addDescription(` + **Test Objective:** Verify that profile status reflect correct on DM list, friend list + **Test Steps:** + 1. User A set profile status to 'Idle' for '30 Minutes' + 2. Verify that new profile status reflect correct on DM list, friend list + + **Expected Result:** Profile status reflect correct on DM list, friend list + `); + + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + await friendPageA.cleanupFriendRelationships(userNameC); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + }); + + await AllureReporter.step("User A set profile status to 'Idle' for '30 Minutes'", async () => { + const currentProfileStatus = await profilePageA.getProfileStatusInFooterProfile(); + await profilePageA.openFooterProfileModal(); + await profilePageA.openSelectProfileStatusModal(currentProfileStatus); + + await profilePageA.setProfileStatus('Do Not Disturb', 'For 30 Minutes'); + }); + + await AllureReporter.step('verify it is visible on friends list', async () => { + const currentProfileStatus = await profilePageA.getProfileStatusInFooterProfile(); + await friendPageB.clickTabAll(); + await friendPageB.searchFriend(userNameA); + const friendLocator = await friendPageB.getFriend(userNameA); + await profilePageB.verifyNewProfileStatusVisibleDueToLocator( + friendLocator, + currentProfileStatus + ); + }); + + await AllureReporter.step('verify it is visible on DM list', async () => { + const currentProfileStatus = await profilePageA.getProfileStatusInFooterProfile(); + await friendPageB.createDM(userNameA); + const userLocator = await friendPageB.getDMByUsername(userNameA); + await profilePageB.verifyNewProfileStatusVisibleDueToLocator( + userLocator, + currentProfileStatus + ); + }); + }); + + test('Verify that profile status reflect correct on DM profile, header DM', async ({ dual }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64803', + github_issue: '10162', + }); + const { pageA, pageB } = dual; + const messagePageB = new MessagePage(pageB); + const profilePageA = new ProfilePage(pageA); + const profilePageB = new ProfilePage(pageB); + const friendPageB = new FriendPage(pageB); + const friendPageA = new FriendPage(pageA); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that profile status reflect correct on DM profile, header DM + **Test Steps:** + 1. User A set profile status to 'Idle' for '30 Minutes' + 2. Verify that new profile status reflect correct on DM profile, header DM + + **Expected Result:** Profile status reflect correct on DM profile, header DM + `); + + await AllureReporter.step(CLEANUP_STEP_NAME, async () => { + await FriendHelper.cleanupMutualFriendRelationships( + friendPageA, + friendPageB, + userNameA, + userNameB + ); + await friendPageA.cleanupFriendRelationships(userNameC); + }); + + await AllureReporter.step(SEND_REQUEST_STEP_NAME, async () => { + await friendPageA.sendFriendRequestToUser(userNameB); + await friendPageA.verifySentRequestToast(); + }); + + await AllureReporter.step('User B accepts the friend request', async () => { + await friendPageB.verifyReceivedRequestToast(`${userNameA} wants to add you as a friend`); + await friendPageB.acceptFirstFriendRequest(); + }); + + await AllureReporter.step('Verify both users see each other as friends', async () => { + await friendPageA.assertAllFriend(userNameB); + await friendPageB.assertAllFriend(userNameA); + }); + + await AllureReporter.step("User A set profile status to 'Idle' for '30 Minutes'", async () => { + const currentProfileStatus = await profilePageA.getProfileStatusInFooterProfile(); + await profilePageA.openFooterProfileModal(); + await profilePageA.openSelectProfileStatusModal(currentProfileStatus); + + await profilePageA.setProfileStatus('Do Not Disturb', 'For 30 Minutes'); + }); + + const currentProfileStatus = await profilePageA.getProfileStatusInFooterProfile(); + + await AllureReporter.step('verify it is visible on header DM', async () => { + await friendPageB.createDM(userNameA); + + const headerDMLocator = await messagePageB.getHeaderDM(); + await profilePageB.verifyNewProfileStatusVisibleDueToLocator( + headerDMLocator, + currentProfileStatus + ); + }); + + test('verify it is visible on profile of DM', async () => { + await messagePageB.openUserProfile(); + const profileLocator = await profilePageB.getUserProfileLocator(); + await profilePageB.verifyNewProfileStatusVisibleDueToLocator( + profileLocator, + currentProfileStatus + ); + }); + }); +}); diff --git a/src/tests/web/ChannelManagement/ChannelManagement_3.spec.ts b/src/tests/web/ChannelManagement/ChannelManagement_3.spec.ts index 2cb3a50a..4bfff882 100644 --- a/src/tests/web/ChannelManagement/ChannelManagement_3.spec.ts +++ b/src/tests/web/ChannelManagement/ChannelManagement_3.spec.ts @@ -122,4 +122,61 @@ test.describe('Channel Management - Module 2', () => { await AllureReporter.attachScreenshot(page, `Text Channel deleted - ${channelName}`); }); + test('Verify that permission settings not visible on stream channel and voice channel', async ({ + page, + }) => { + await AllureReporter.addWorkItemLinks({ + tms: '64610', + }); + await AllureReporter.addTestParameters({ + testType: AllureConfig.TestTypes.E2E, + userType: AllureConfig.UserTypes.AUTHENTICATED, + severity: AllureConfig.Severity.NORMAL, + }); + await AllureReporter.addDescription(` + **Test Objective:** Verify that permission settings are not visible on stream channel and voice channel. + Steps: + 1. Create a stream channel + 2. Verify that permission settings not visible on stream channel + 3. Create a voice channel + 4. Verify that permission settings not visible on voice channel + **Expected Result:** Permission settings are not visible on stream channel and voice channel. + `); + await AllureReporter.addLabels({ + tag: ['stream-channel', 'voice-channel', 'permission-settings'], + }); + const unique = Date.now().toString(36).slice(-6); + const streamChannelName = `sc-${unique}`.slice(0, 20); + const voiceChannelName = `vc-${unique}`.slice(0, 20); + const clanPage = new ClanPage(page); + const channelSettings = new ChannelSettingPage(page); + await AllureReporter.addParameter('streamChannelName', streamChannelName); + await AllureReporter.addParameter('voiceChannelName', voiceChannelName); + await AllureReporter.step(`Create new stream channel: ${streamChannelName}`, async () => { + await clanPage.createNewChannel(ChannelType.STREAM, streamChannelName); + const isStreamChannelPresent = await clanPage.isNewChannelPresent(streamChannelName); + expect(isStreamChannelPresent).toBe(true); + }); + await AllureReporter.step( + 'Verify that permission settings not visible on stream channel', + async () => { + await clanPage.openChannelSettings(streamChannelName); + const isPermissionSettingsVisible = await channelSettings.isPermissionSettingsVisible(); + expect(isPermissionSettingsVisible).toBe(false); + } + ); + await AllureReporter.step(`Create new voice channel: ${voiceChannelName}`, async () => { + await clanPage.createNewChannel(ChannelType.VOICE, voiceChannelName); + const isVoiceChannelPresent = await clanPage.isNewChannelPresent(voiceChannelName); + expect(isVoiceChannelPresent).toBe(true); + }); + await AllureReporter.step( + 'Verify that permission settings not visible on voice channel', + async () => { + await clanPage.openChannelSettings(voiceChannelName); + const isPermissionSettingsVisible = await channelSettings.isPermissionSettingsVisible(); + expect(isPermissionSettingsVisible).toBe(false); + } + ); + }); }); diff --git a/src/tests/web/ChannelMessage/ChannelMessage_8.spec.ts b/src/tests/web/ChannelMessage/ChannelMessage_8.spec.ts new file mode 100644 index 00000000..b57866ba --- /dev/null +++ b/src/tests/web/ChannelMessage/ChannelMessage_8.spec.ts @@ -0,0 +1,151 @@ +import { AllureReporter } from '@/utils/allureHelpers'; +import { AuthHelper } from '@/utils/authHelper'; +import { ClanSetupHelper } from '@/utils/clanSetupHelper'; +import test, { expect } from '@playwright/test'; +import { AccountCredentials } from '../../../config/environment'; + +import { ClanFactory } from '@/data/factories/ClanFactory'; +import { ClanPage } from '@/pages/Clan/ClanPage'; +import { MessagePage } from '@/pages/MessagePage'; +import { ChannelType } from '@/types/clan-page.types'; +import TestSuiteHelper from '@/utils/testSuite.helper'; +import { MessageTestHelpers } from '../../../utils/messageHelpers'; + +test.describe('Channel Message - Module 8', () => { + const clanFactory = new ClanFactory(); + const credentials = AccountCredentials.accountKien2; + test.beforeAll(async ({ browser }) => { + await TestSuiteHelper.setupBeforeAll({ + browser, + clanFactory, + configs: ClanSetupHelper.configs.channelMessage8, + credentials, + }); + }); + + test.beforeEach(async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + parrent_issue: '63366', + }); + await TestSuiteHelper.setupBeforeEach({ + page, + clanFactory, + credentials, + }); + }); + + test.afterAll(async ({ browser }) => { + await TestSuiteHelper.onAfterAll({ + browser, + clanFactory, + credentials, + }); + }); + + test.afterEach(async ({ page }) => { + await AuthHelper.logout(page); + }); + + test('Forward all messages to general channel and verify order and content', async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + tms: '63395', + }); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can forward all messages from a text channel to general channel + and messages are displayed with correct order and content + + **Test Steps:** + 1. Create a new text channel + 2. Send 3 different messages to the channel + 3. Forward all messages to general channel + 4. Verify messages in general channel have correct order and content + + **Expected Result:** All messages are forwarded to general channel with correct order and content + `); + + await AllureReporter.addLabels({ + tag: ['channel-message', 'forward', 'general-channel'], + }); + + const messageHelper = new MessageTestHelpers(page); + const clanPage = new ClanPage(page); + + const channelName = `auto-text-channel-${Date.now()}`; + const messages = [ + `Forward message 1 - ${Date.now()}`, + `Forward message 2 - ${Date.now()}`, + `Forward message 3 - ${Date.now()}`, + ]; + + await AllureReporter.step('Create a new text channel', async () => { + await clanPage.createNewChannel(ChannelType.TEXT, channelName); + const isNewChannelPresent = await clanPage.isNewChannelPresent(channelName); + expect(isNewChannelPresent).toBe(true); + }); + + await AllureReporter.step('Send 3 messages to the text channel', async () => { + for (const message of messages) { + await messageHelper.sendTextMessage(message); + } + }); + + await AllureReporter.step('Forward all messages to general channel', async () => { + await messageHelper.forwardAllMessages('general'); + }); + + await AllureReporter.step('Verify forwarded messages in general channel', async () => { + await clanPage.openChannelByName('general'); + + await messageHelper.assertMessageFromLastByIndexAndContent(2, messages[0]); + await messageHelper.assertMessageFromLastByIndexAndContent(1, messages[1]); + await messageHelper.assertMessageFromLastByIndexAndContent(0, messages[2]); + }); + }); + + test('Verify that user can unpin messages after pin them in a text channel', async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + tms: '63400', + }); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can unpin message after pin them in a text channel + **Test Steps:** + 1. Create a new text channel + 2. Send a message to the channel + 3. Pin message + 4. Unpin the message + 5. Verify the message is unpinned + **Expected Result:** User can unpin the message + `); + + await AllureReporter.addLabels({ + tag: ['channel-message', 'pin-unpin', 'text-channel'], + }); + const messageHelper = new MessageTestHelpers(page); + const messagePage = new MessagePage(page); + const clanPage = new ClanPage(page); + const channelName = `auto-text-channel-${Date.now()}`; + const message = `Pin message- ${Date.now()}`; + + await AllureReporter.step('Create a new text channel', async () => { + await clanPage.createNewChannel(ChannelType.TEXT, channelName); + const isNewChannelPresent = await clanPage.isNewChannelPresent(channelName); + expect(isNewChannelPresent).toBe(true); + }); + + await AllureReporter.step('Send message to the text channel', async () => { + await messageHelper.sendTextMessage(message); + }); + await AllureReporter.step('Pin message', async () => { + await messagePage.pinLastMessage(); + }); + + await AllureReporter.step('Unpin the message', async () => { + await messagePage.unpinLastMessage(); + }); + await AllureReporter.step('Verify the message is unpinned', async () => { + const isMessageUnpinned = await messagePage.verifyMessageIsUnpinned(message); + expect(isMessageUnpinned).toBe(true); + }); + }); +}); diff --git a/src/tests/web/ChannelMessage/channelMessage_7.spec.ts b/src/tests/web/ChannelMessage/channelMessage_7.spec.ts index 9478a7fd..d1bae3bf 100644 --- a/src/tests/web/ChannelMessage/channelMessage_7.spec.ts +++ b/src/tests/web/ChannelMessage/channelMessage_7.spec.ts @@ -6,6 +6,7 @@ import { AccountCredentials } from '../../../config/environment'; import { ClanFactory } from '@/data/factories/ClanFactory'; import { ClanPage } from '@/pages/Clan/ClanPage'; +import { MessagePage } from '@/pages/MessagePage'; import { ChannelType } from '@/types/clan-page.types'; import TestSuiteHelper from '@/utils/testSuite.helper'; import { MessageTestHelpers } from '../../../utils/messageHelpers'; @@ -145,4 +146,122 @@ test.describe('Channel Message - Module 7', () => { await messageHelper.isGifVisibleOnGalleryTab(gifName); }); }); + + test('Verify that user can send message with anonymous', async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + tms: '63368', + github_issue: '10991', + }); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can send message with anonymous in a channel + + **Test Steps:** + 1. Navigate to a channel + 2. Enable anonymous mode using Ctrl+Shift+Enter + 3. Verify anonymous icon is visible + 4. Send a message with anonymous identity + 5. Verify the message is displayed with anonymous profile + + **Expected Result:** User can send message with anonymous identity + `); + + await AllureReporter.addLabels({ + tag: ['channel-message', 'anonymous', 'text-channel'], + }); + + const messageText = `Anonymous message ${Date.now()}`; + const messagePage = new MessagePage(page); + + await AllureReporter.step('Enable anonymous mode and send message', async () => { + await messagePage.sendMessageWithAnonymous(messageText); + }); + + await AllureReporter.step( + 'Verify anonymous message is sent with anonymous identity', + async () => { + const isMessageSent = await messagePage.isAnonymousMessageSent(); + expect(isMessageSent).toBe(true); + } + ); + }); + + test('Verify that user can not send message with anonymous when settings prevent anonymous', async ({ + page, + }) => { + await AllureReporter.addWorkItemLinks({ + tms: '63368', + github_issue: '10991', + }); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can not send message with anonymous when settings prevent anonymous + + **Test Steps:** + 1. Setting prevent anonymous in clan settings + 1. Navigate to a channel + 2. Enable anonymous mode using Ctrl+Shift+Enter + 3. Verify anonymous icon is not visible + + **Expected Result:** User can not send message with anonymous when settings prevent anonymous + `); + + await AllureReporter.addLabels({ + tag: ['channel-message', 'anonymous', 'text-channel', 'prevent-anonymous'], + }); + + const messagePage = new MessagePage(page); + const clanPage = new ClanPage(page); + + await AllureReporter.step('Setting prevent anonymous', async () => { + await clanPage.openClanSettings(); + await clanPage.preventAnonymous(); + await clanPage.closeSettingsClan(); + }); + + await AllureReporter.step('Enable anonymous mode', async () => { + await page.keyboard.press('Control+Shift+Enter'); + await page.waitForTimeout(3000); + }); + + await AllureReporter.step('Verify anonymous icon is not visible', async () => { + const isVisible = await messagePage.isAnonymousIconVisible(); + expect(isVisible).toBe(false); + }); + }); + + test('Add message to inbox and verify message is present in inbox', async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + tms: '65128', + github_issue: '11057', + }); + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can add message to inbox and verify message is present in inbox + **Test Steps:** + 1. Send message on channel + 2. Add message to inbox + 3. Open inbox + 4. Verify message is present in inbox + **Expected Result:** User can add message to inbox and verify message is present in inbox + `); + await AllureReporter.addLabels({ + tag: ['channel-message', 'inbox', 'add-to-inbox'], + }); + const messageHelper = new MessageTestHelpers(page); + const messageContent = `Test inbox message ${Date.now()}`; + await AllureReporter.step('Send message on channel', async () => { + await messageHelper.sendTextMessage(messageContent); + }); + + await AllureReporter.step('Add message to inbox', async () => { + const targetMessage = await messageHelper.findMessageItemByText(messageContent); + await messageHelper.addMessageToInbox(targetMessage); + }); + + await AllureReporter.step('Open inbox and verify message is present in inbox', async () => { + await messageHelper.openHeaderInboxButton(); + await messageHelper.openMessageTabInInbox(); + await messageHelper.assertMessageInInboxByContent(messageContent); + }); + }); }); diff --git a/src/tests/web/DirectMessage.spec.ts b/src/tests/web/DirectMessage/DirectMessage.spec.ts similarity index 100% rename from src/tests/web/DirectMessage.spec.ts rename to src/tests/web/DirectMessage/DirectMessage.spec.ts diff --git a/src/tests/web/DirectMessage/DirectMessage1.spec.ts b/src/tests/web/DirectMessage/DirectMessage1.spec.ts new file mode 100644 index 00000000..932e7e22 --- /dev/null +++ b/src/tests/web/DirectMessage/DirectMessage1.spec.ts @@ -0,0 +1,170 @@ +import { AllureConfig } from '@/config/allure.config'; +import { AccountCredentials } from '@/config/environment'; +import { ClanFactory } from '@/data/factories/ClanFactory'; +import MessageSelector from '@/data/selectors/MessageSelector'; +import { ClanPage } from '@/pages/Clan/ClanPage'; +import { MessagePage } from '@/pages/MessagePage'; +import { MezonCredentials } from '@/types'; +import { ChannelStatus, ChannelType } from '@/types/clan-page.types'; +import { AllureReporter } from '@/utils/allureHelpers'; +import { AuthHelper } from '@/utils/authHelper'; +import { ClanSetupHelper } from '@/utils/clanSetupHelper'; +import { getUsernamesFromEmails } from '@/utils/dualTestHelper'; +import { MessageTestHelpers } from '@/utils/messageHelpers'; +import TestSuiteHelper from '@/utils/testSuite.helper'; +import { expect, test } from '@playwright/test'; + +test.describe('Direct Message 1 - Invoice Status', () => { + const clanFactory = new ClanFactory(); + const credentials: MezonCredentials = AccountCredentials.account2; + const [userNameA] = getUsernamesFromEmails([credentials.email]); + + test.beforeAll(async ({ browser }) => { + await TestSuiteHelper.setupBeforeAll({ + browser, + clanFactory, + configs: ClanSetupHelper.configs.directMessage1, + credentials, + }); + }); + + test.beforeEach(async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + parrent_issue: '63370', + }); + + await TestSuiteHelper.setupBeforeEach({ + page, + clanFactory, + credentials, + }); + }); + + test.afterAll(async ({ browser }) => { + await TestSuiteHelper.onAfterAll({ + browser, + clanFactory, + credentials, + }); + }); + + test.afterEach(async ({ page }) => { + await AuthHelper.logout(page); + }); + + test('Display invoice status in DM list and header', async ({ page }) => { + await AllureReporter.addTestParameters({ + testType: AllureConfig.TestTypes.E2E, + userType: AllureConfig.UserTypes.AUTHENTICATED, + severity: AllureConfig.Severity.CRITICAL, + }); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that invoice (in-voice) status indicator is visible on the direct message friend list and in the DM chat header when a user is in an active voice call. + + **Test Steps:** + 1. Create a new voice channel + 2. Join the voice channel + 3. Navigate to direct friends page + 4. Open DM conversation with the current user (self) + 5. Verify invoice status indicator in friend list + 6. Verify invoice status indicator in DM header + + **Expected Result:** Invoice status indicator is visible on the friend list item and in the DM chat header when user is in an active voice session. + `); + + await AllureReporter.addLabels({ + tag: ['direct-message', 'invoice-status', 'voice-status'], + }); + + const messageSelector = new MessageSelector(page); + const clanPage = new ClanPage(page); + const messagePage = new MessagePage(page); + const messageHelper = new MessageTestHelpers(page); + + // Create a voice channel + const voiceChannelName = `voice-${Date.now().toString(36).slice(-6)}`.slice(0, 20); + + await AllureReporter.addParameter('voiceChannelName', voiceChannelName); + await AllureReporter.addParameter('channelType', ChannelType.VOICE); + await AllureReporter.addParameter('channelStatus', ChannelStatus.PUBLIC); + + await AllureReporter.step(`Create new public voice channel: ${voiceChannelName}`, async () => { + const channelCreated = await clanPage.createNewChannel( + ChannelType.VOICE, + voiceChannelName, + ChannelStatus.PUBLIC + ); + expect(channelCreated).toBe(true); + }); + + await AllureReporter.step('Verify voice channel is present', async () => { + const channelExists = await clanPage.isNewChannelPresent(voiceChannelName); + expect(channelExists).toBe(true); + }); + + await AllureReporter.step(`Join voice channel: ${voiceChannelName}`, async () => { + const joinedVoice = await clanPage.joinVoiceChannel(voiceChannelName); + expect(joinedVoice).toBe(true); + await page.waitForTimeout(2000); + }); + + await AllureReporter.step('Verify user is in voice channel', async () => { + const isInVoiceChannel = await clanPage.isJoinVoiceChannel(voiceChannelName); + expect(isInVoiceChannel).toBe(true); + }); + + await AllureReporter.step(`Open DM conversation with current user: ${userNameA}`, async () => { + await messagePage.openSearchModalbyPressCtrlK(); + await messageHelper.openDMByNameOnsearchModal(userNameA); + await page.waitForTimeout(1500); + }); + + await AllureReporter.step('Verify invoice status indicator in friend list', async () => { + const invoiceStatusInFriendList = messageSelector.invoiceStatusFriendList; + const friendListStatusVisible = await invoiceStatusInFriendList + .isVisible({ timeout: 5000 }) + .catch(() => false); + + if (friendListStatusVisible) { + await expect(invoiceStatusInFriendList).toBeVisible(); + } + + return friendListStatusVisible; + }); + + await AllureReporter.step('Verify invoice status indicator in DM header', async () => { + const invoiceStatusInHeader = messageSelector.invoiceStatusDMHeader; + const headerStatusVisible = await invoiceStatusInHeader + .isVisible({ timeout: 5000 }) + .catch(() => false); + + if (headerStatusVisible) { + await expect(invoiceStatusInHeader).toBeVisible(); + } + + return headerStatusVisible; + }); + + await AllureReporter.step( + 'Verify invoice status is visible on friend list or DM header', + async () => { + const invoiceStatusInFriendList = messageSelector.invoiceStatusFriendList; + const invoiceStatusInHeader = messageSelector.invoiceStatusDMHeader; + + const friendListStatusVisible = await invoiceStatusInFriendList + .isVisible({ timeout: 5000 }) + .catch(() => false); + + const headerStatusVisible = await invoiceStatusInHeader + .isVisible({ timeout: 5000 }) + .catch(() => false); + + // At least one indicator should be visible when user is in voice + expect(friendListStatusVisible || headerStatusVisible).toBe(true); + } + ); + + await AllureReporter.attachScreenshot(page, `Invoice Status - DM List & Header - ${userNameA}`); + }); +}); diff --git a/src/tests/web/OnboardingGuide.spec.ts b/src/tests/web/OnboardingGuide.spec.ts index 4008f458..3d9dcf94 100644 --- a/src/tests/web/OnboardingGuide.spec.ts +++ b/src/tests/web/OnboardingGuide.spec.ts @@ -176,4 +176,195 @@ test.describe('Onboarding Guide Task Completion', () => { await AllureReporter.attachScreenshot(page, 'Create Channel Task Completed'); }); + + test('Verify that user can enable onboarding on clan settings', async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + tms: '63368', + github_issue: '10991', + }); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that user can enable onboarding on clan settings + + **Test Steps:** + 1. Open clan settings -> onboarding tab + 2. Click enable onboarding + 3. Click setup question + 4. Add task + 5. Add question, answer title, answer description and save + + **Expected Result:** Verify that user can enable onboarding on clan settings + `); + + await AllureReporter.addLabels({ + tag: ['clan-setting', 'onboarding', 'pre-question', 'enable'], + }); + + const clanPage = new ClanPage(page); + const onboardingPage = new OnboardingPage(page); + const data = { + question: 'This is question', + answerTitle: 'This is answer title', + answerDescription: 'This is answer description', + taskName: 'Send a message', + }; + + await AllureReporter.step('Enable onboarding on clan', async () => { + await clanPage.openClanSettings(); + await onboardingPage.verifyEnableOnboardingOnClanSettingsSidebar(false); + await onboardingPage.openOnboardingTab(); + await onboardingPage.clickEnableOnboarding(); + }); + + await AllureReporter.step('Enable onboarding', async () => { + await onboardingPage.addTaskOnboarding(data.taskName); + await onboardingPage.clickBackOnboardingModal(); + await onboardingPage.addPrequestionOnboaring( + data.question, + data.answerTitle, + data.answerDescription + ); + }); + + await AllureReporter.step('Verify onboarding is enable', async () => { + await onboardingPage.verifyEnableOnboardingOnClanSettingsSidebar(); + }); + }); + + test('Verify that onboarding page is visible and data is match', async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + tms: '63368', + github_issue: '10991', + }); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that onboarding page is visible and data is match + + **Test Steps:** + 1. Open clan settings -> onboarding tab + 2. Click enable onboarding + 3. Click setup question + 4. Add question, answer title, answer description and save + 5. Verify pre-question is visible on preview + + **Expected Result:** Verify that onboarding page is visible and data is match + `); + + await AllureReporter.addLabels({ + tag: ['clan-setting', 'onboarding', 'task'], + }); + + const clanPage = new ClanPage(page); + const onboardingPage = new OnboardingPage(page); + const data = { + question: 'This is question', + answerTitle: 'This is answer title', + answerDescription: 'This is answer description', + taskName: 'Send a message', + }; + + await AllureReporter.step('Enable onboarding on clan', async () => { + await clanPage.openClanSettings(); + await onboardingPage.verifyEnableOnboardingOnClanSettingsSidebar(false); + await onboardingPage.openOnboardingTab(); + await onboardingPage.clickEnableOnboarding(); + }); + + await AllureReporter.step('Enable onboarding', async () => { + await onboardingPage.addTaskOnboarding(data.taskName); + await onboardingPage.clickBackOnboardingModal(); + await onboardingPage.addPrequestionOnboaring( + data.question, + data.answerTitle, + data.answerDescription + ); + }); + + await AllureReporter.step('Verify onboarding is enable', async () => { + await onboardingPage.verifyEnableOnboardingOnClanSettingsSidebar(); + await clanPage.closeSettingsClan(); + }); + + await AllureReporter.step('Verify onboarding page is enable and data is match', async () => { + await onboardingPage.verifyOnboardingPageVisible(); + await onboardingPage.openOnboardingPage(); + await onboardingPage.verifyOnboardingSetupByType( + 'question', + data.answerTitle, + data.answerDescription, + data.question + ); + await onboardingPage.verifyOnboardingSetupByType('mission', data.taskName); + }); + }); + + test('Verify that onboarding settings is match on preview mode', async ({ page }) => { + await AllureReporter.addWorkItemLinks({ + tms: '63368', + github_issue: '10991', + }); + + await AllureReporter.addDescription(` + **Test Objective:** Verify that onboarding settings is match on preview mode + + **Test Steps:** + 1. Open clan settings -> onboarding tab + 2. Click enable onboarding + 3. Click setup question + 4. Add question, answer title, answer description and save + 5. Verify pre-question is visible on preview + + **Expected Result:** Verify that onboarding settings is match on preview mode + `); + + await AllureReporter.addLabels({ + tag: ['clan-setting', 'onboarding', 'task', 'preview-mode'], + }); + + const clanPage = new ClanPage(page); + const onboardingPage = new OnboardingPage(page); + const data = { + question: 'This is question', + answerTitle: 'This is answer title', + answerDescription: 'This is answer description', + taskName: 'Send a message', + }; + + await AllureReporter.step('Enable onboarding on clan', async () => { + await clanPage.openClanSettings(); + await onboardingPage.verifyEnableOnboardingOnClanSettingsSidebar(false); + await onboardingPage.openOnboardingTab(); + await onboardingPage.clickEnableOnboarding(); + }); + + await AllureReporter.step('Enable onboarding', async () => { + await onboardingPage.addTaskOnboarding(data.taskName); + await onboardingPage.clickBackOnboardingModal(); + await onboardingPage.addPrequestionOnboaring( + data.question, + data.answerTitle, + data.answerDescription + ); + }); + + await AllureReporter.step('Verify onboarding is enable', async () => { + await onboardingPage.verifyEnableOnboardingOnClanSettingsSidebar(); + }); + + await AllureReporter.step( + 'Verify that onboarding settings is match on preview mode', + async () => { + await onboardingPage.openOnboardingPreviewMode(); + await onboardingPage.verifyOnboardingSetupByType( + 'question', + data.answerTitle, + data.answerDescription, + data.question + ); + await onboardingPage.verifyOnboardingSetupByType('mission', data.taskName); + await onboardingPage.closeOnboardingPreviewMode(); + await clanPage.closeSettingsClan(); + } + ); + }); }); diff --git a/src/utils/clanSetupHelper.ts b/src/utils/clanSetupHelper.ts index 3d9e9e26..3bc142aa 100644 --- a/src/utils/clanSetupHelper.ts +++ b/src/utils/clanSetupHelper.ts @@ -157,6 +157,11 @@ export class ClanSetupHelper { suiteName: 'Channel Message - Module 7', }), + channelMessage8: ClanSetupHelper.createConfig({ + clanNamePrefix: 'MessageTestClan', + suiteName: 'Channel Message - Module 8', + }), + clanManagement: ClanSetupHelper.createConfig({ clanNamePrefix: 'ClanManagementTest', suiteName: 'Clan Management', @@ -236,6 +241,11 @@ export class ClanSetupHelper { suiteName: 'Direct Message', }), + directMessage1: ClanSetupHelper.createConfig({ + clanNamePrefix: 'DirectMessageTest', + suiteName: 'Direct Message 1', + }), + topicMessage: ClanSetupHelper.createConfig({ clanNamePrefix: 'TopicMessageTest', suiteName: 'Topic Message', diff --git a/src/utils/messageHelpers.ts b/src/utils/messageHelpers.ts index ade7a323..91f99678 100644 --- a/src/utils/messageHelpers.ts +++ b/src/utils/messageHelpers.ts @@ -1,5 +1,7 @@ import FriendSelector from '@/data/selectors/FriendSelector'; import MessageSelector from '@/data/selectors/MessageSelector'; +import { FriendPage } from '@/pages/FriendPage'; +import { LoginPage } from '@/pages/LoginPage'; import { expect, Locator, Page } from '@playwright/test'; import { generateE2eSelector } from './generateE2eSelector'; @@ -2322,6 +2324,12 @@ export class MessageTestHelpers { await expect(tooltip).toBeVisible({ timeout: 5000 }); } + async openMessageTabInInbox() { + await expect(this.selector.messageInboxPopover.triggerTab).toBeVisible({ timeout: 5000 }); + await this.selector.messageInboxPopover.triggerTab.click(); + await this.page.waitForTimeout(500); + } + async assertMessageInInboxByContent(messageContent: string) { const inboxMessage = this.selector.inboxMessages.filter({ hasText: messageContent }); await expect(inboxMessage).toBeVisible({ timeout: 5000 }); @@ -2584,6 +2592,104 @@ export class MessageTestHelpers { await expect(deleteMessageButton).toBeHidden({ timeout: 3000 }); } } + + async verifyShareContactModalVisible() { + const shareContactModal = this.selector.shareContact.modal.item; + await expect(shareContactModal).toBeVisible({ timeout: 3000 }); + } + + async shareContactInDMOrChannel(destination: string, shouldVisible = true) { + const shareContactInput = this.selector.shareContact.modal.inputSearch; + await expect(shareContactInput).toBeVisible({ timeout: 3000 }); + await shareContactInput.fill(destination); + await this.page.waitForTimeout(3000); + const destinationItemLocator = this.selector.modalForwardMessage.locator( + generateE2eSelector('suggest_item'), + { + hasText: destination, + } + ); + const isVisible = await destinationItemLocator.isVisible(); + if (!shouldVisible) { + expect(isVisible).toBeFalsy(); + } else { + expect(isVisible).toBeTruthy(); + await destinationItemLocator.first().click(); + await this.selector.shareContact.modal.buttonShare.click(); + await this.page.waitForTimeout(2000); + } + } + + async verifyContactSharedInDMOrChannel(username: string) { + const lastMessage = this.selector.messages.last(); + const contactLocator = lastMessage.locator(this.selector.shareContact.card); + await expect(contactLocator).toBeVisible({ timeout: 3000 }); + const nameLocator = contactLocator.locator(this.selector.shareContact.username); + await expect(nameLocator).toHaveText(username, { timeout: 3000 }); + } + + async verifyCallItemVisibleInShareContactCard(username: string, shouldVisible = true) { + const friendPage = new FriendPage(this.page); + const loginPage = new LoginPage(this.page); + const lastMessage = this.selector.messages.last(); + const contactLocator = lastMessage.locator(this.selector.shareContact.card); + await expect(contactLocator).toBeVisible({ timeout: 3000 }); + const nameLocator = contactLocator.locator(this.selector.shareContact.username); + await expect(nameLocator).toHaveText(username, { timeout: 3000 }); + const callItemLocator = contactLocator.locator(this.selector.shareContact.buttonCall); + await callItemLocator.click(); + if (shouldVisible) { + const currentUrl = loginPage.getCurrentUrl(); + expect(currentUrl).toContain('chat/direct/message'); + } else { + await friendPage.verifyReceivedRequestToast(`You cannot call yourself.`); + } + } + + async clickMessageOnShareContactCard() { + const lastMessage = this.selector.messages.last(); + const contactLocator = lastMessage.locator(this.selector.shareContact.card); + await expect(contactLocator).toBeVisible({ timeout: 3000 }); + const buttonMessageLocator = contactLocator.locator(this.selector.shareContact.buttonMessage); + await expect(buttonMessageLocator).toBeVisible({ timeout: 3000 }); + await buttonMessageLocator.click(); + await this.page.waitForTimeout(2000); + } + + async addMessageToInbox(messageElement: Locator): Promise { + await messageElement.click({ button: 'right' }); + await this.page.waitForTimeout(1000); + + await this.selector.addToInboxButton.click(); + + await this.page.waitForTimeout(500); + } + + async forwardAllMessages(destination: string) { + const messages = this.selector.messages.last(); + await messages.click({ button: 'right' }); + await this.page.waitForTimeout(400); + await this.selector.forwardAllMessagesButton.click(); + await this.page.waitForTimeout(500); + await this.selector.searchUserOnForwardMessageModal.fill(destination); + await this.page.waitForTimeout(3000); + const channelItemLocator = this.selector.modalForwardMessage.locator( + generateE2eSelector('suggest_item'), + { + hasText: destination, + } + ); + await channelItemLocator.waitFor({ state: 'visible', timeout: 5000 }); + await channelItemLocator.first().click(); + await this.selector.sendForwardMessageButton.click(); + } + + async assertMessageFromLastByIndexAndContent(indexFromLast: number, messageContent: string) { + const messageLocator = this.selector.messages.nth(-(indexFromLast + 1)); + + await expect(messageLocator).toBeVisible({ timeout: 5000 }); + await expect(messageLocator).toHaveText(messageContent, { timeout: 5000 }); + } } export const LINK_TEST_URLS = [