From 2ed9c1dd0de7341a4183622581099df1a2652727 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Tue, 11 Mar 2025 21:08:30 +0200 Subject: [PATCH 001/128] initial setup --- .../.github/workflows/playwright.yml | 27 ++ tests/playwright/.gitignore | 8 + tests/playwright/README.md | 44 ++ tests/playwright/package.json | 23 + tests/playwright/playwright.config.ts | 115 +++++ .../tests-examples/demo-todo-app.spec.ts | 437 ++++++++++++++++++ tests/playwright/tests/example.spec.ts | 18 + tests/playwright/yarn.lock | 84 ++++ 8 files changed, 756 insertions(+) create mode 100644 tests/playwright/.github/workflows/playwright.yml create mode 100644 tests/playwright/.gitignore create mode 100644 tests/playwright/README.md create mode 100644 tests/playwright/package.json create mode 100644 tests/playwright/playwright.config.ts create mode 100644 tests/playwright/tests-examples/demo-todo-app.spec.ts create mode 100644 tests/playwright/tests/example.spec.ts create mode 100644 tests/playwright/yarn.lock diff --git a/tests/playwright/.github/workflows/playwright.yml b/tests/playwright/.github/workflows/playwright.yml new file mode 100644 index 0000000000..a94b6417ac --- /dev/null +++ b/tests/playwright/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm install -g yarn && yarn + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Playwright tests + run: yarn playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/tests/playwright/.gitignore b/tests/playwright/.gitignore new file mode 100644 index 0000000000..b7461d498c --- /dev/null +++ b/tests/playwright/.gitignore @@ -0,0 +1,8 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/allure-results/ diff --git a/tests/playwright/README.md b/tests/playwright/README.md new file mode 100644 index 0000000000..bce27eac30 --- /dev/null +++ b/tests/playwright/README.md @@ -0,0 +1,44 @@ + +## Install + +Install Playwright browsers - 'yarn playwright install' + +Install Playwright operating system dependencies requires sudo / root - 'sudo yarn playwright install-deps' + + + +## Extra tooling +Inside that directory, you can run several commands: + +Runs the end-to-end tests. + +```yarn playwright test``` + +Starts the interactive UI mode. +```yarn playwright test --ui``` + + +yarn playwright test --project=chromium +Runs the tests only on Desktop Chrome. + +yarn playwright test example +Runs the tests in a specific file. + +yarn playwright test --debug +Runs the tests in debug mode. + +yarn playwright codegen +Auto generate tests with Codegen. + + + +Allure report needs JAVA_HOME set +and to run the server JDK version 8 to 11 otherwise you get +Starting web server... +Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: /usr/lib/jvm/java-17-openjdk-amd64/lib/libawt_xawt.so +at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2398) +at java.base/java.lang.Runtime.load0(Runtime.java:755) +at java.base/java.lang.System.load(System.java:1970) +at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) + + diff --git a/tests/playwright/package.json b/tests/playwright/package.json new file mode 100644 index 0000000000..109e68c5de --- /dev/null +++ b/tests/playwright/package.json @@ -0,0 +1,23 @@ +{ + "name": "playwright", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "devDependencies": { + "@playwright/test": "^1.51.0", + "@types/node": "^22.13.10", + "allure-commandline": "^2.33.0", + "allure-js-commons": "^3.2.0", + "allure-playwright": "^3.2.0" + }, + "scripts": { + "allTests": "playwright test", + "generateAndShowReports": "allure serve allure-results", + "generateReports": "allure generate --clean", + + "clean:results": "rm -rf allure-results", + "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", + "test:allure": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results" + + } +} diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts new file mode 100644 index 0000000000..56f0c988b1 --- /dev/null +++ b/tests/playwright/playwright.config.ts @@ -0,0 +1,115 @@ +import { defineConfig, devices } from '@playwright/test'; +import * as os from "node:os"; +import {Status} from "allure-js-commons"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [["line"], ["html"], [ + "allure-playwright", + { + resultsDir: "allure-results", + detail: true, + suiteTitle: true, + links: { + issue: { + nameTemplate: "Issue #%s", + urlTemplate: "https://issues.example.com/%s", + }, + tms: { + nameTemplate: "TMS #%s", + urlTemplate: "https://tms.example.com/%s", + }, + jira: { + urlTemplate: (v: any) => `https://jira.example.com/browse/${v}`, + }, + }, + categories: [ + { + name: "foo", + messageRegex: "bar", + traceRegex: "baz", + matchedStatuses: [Status.FAILED, Status.BROKEN], + }, + ], + environmentInfo: { + os_platform: os.platform(), + os_release: os.release(), + os_version: os.version(), + node_version: process.version, + }, + }, + ]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/tests/playwright/tests-examples/demo-todo-app.spec.ts b/tests/playwright/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000000..8641cb5f5d --- /dev/null +++ b/tests/playwright/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +] as const; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts new file mode 100644 index 0000000000..54a906a4e8 --- /dev/null +++ b/tests/playwright/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock new file mode 100644 index 0000000000..66986e02b9 --- /dev/null +++ b/tests/playwright/yarn.lock @@ -0,0 +1,84 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@playwright/test@^1.51.0": + version "1.51.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.0.tgz#8d5c8400b465a0bfdbcf993e390ceecb903ea6d2" + integrity sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA== + dependencies: + playwright "1.51.0" + +"@types/node@^22.13.10": + version "22.13.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" + integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw== + dependencies: + undici-types "~6.20.0" + +allure-commandline@^2.33.0: + version "2.33.0" + resolved "https://registry.yarnpkg.com/allure-commandline/-/allure-commandline-2.33.0.tgz#140560c615ea904ff34c061c4c4b6d43858b2b68" + integrity sha512-oGMW1Zaqd9SqYJHUqeET1AP363guQkswnCKD+6jSX9YCK8BbttSqZJy9PeSmJtU16uW3qGB6cvgrvJwKUWG5Ew== + +allure-js-commons@3.2.0, allure-js-commons@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/allure-js-commons/-/allure-js-commons-3.2.0.tgz#064503cec8735564599c90fff5a239c36d016d66" + integrity sha512-UXRo3D6/XEIMosro+OldWj8phJ65eSOYaAUlThOpl6nJJ0sGngMpJYog+Z9FmZDo1BZn4edwLs4aAUaTgkz4Cg== + dependencies: + md5 "^2.3.0" + +allure-playwright@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/allure-playwright/-/allure-playwright-3.2.0.tgz#4bbb276c6785ee7a90540e0b2c93d6ccf273caa7" + integrity sha512-E9YNqFBXrycMaOs4x5/Tsdl4xN8Ss0yw8XnwcVUzezR3cjlPb5gUdR81G/zQsi+I3mb+UQMS21yORHKTI9W2fw== + dependencies: + allure-js-commons "3.2.0" + +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + +playwright-core@1.51.0: + version "1.51.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.0.tgz#bb23ea6bb6298242d088ae5e966ffcf8dc9827e8" + integrity sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg== + +playwright@1.51.0: + version "1.51.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.51.0.tgz#9ba154497ba62bc6dc199c58ee19295eb35a4707" + integrity sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA== + dependencies: + playwright-core "1.51.0" + optionalDependencies: + fsevents "2.3.2" + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== From edbb6b8fae0a4c0cb6c158e7b6f45b4a6a0f74f5 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Tue, 11 Mar 2025 21:25:28 +0200 Subject: [PATCH 002/128] documentation and some additional scripts --- tests/playwright/README.md | 34 ++++++++++++++++++++-------------- tests/playwright/package.json | 4 ++-- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index bce27eac30..0cd5bb7cd8 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -7,38 +7,44 @@ Install Playwright operating system dependencies requires sudo / root - 'sudo ya -## Extra tooling -Inside that directory, you can run several commands: +## Runs Runs the end-to-end tests. ```yarn playwright test``` -Starts the interactive UI mode. -```yarn playwright test --ui``` +Runs the tests only on Desktop Chrome. +```yarn playwright test --project=chromium``` -yarn playwright test --project=chromium -Runs the tests only on Desktop Chrome. +Runs the tests in debug mode. + +```yarn playwright test --debug``` -yarn playwright test example Runs the tests in a specific file. -yarn playwright test --debug -Runs the tests in debug mode. +```yarn playwright test example``` + +Starts the interactive UI mode. This also can be set in the config + +```yarn playwright test --ui``` + + +## Extra tooling +Inside that directory, you can run several commands: -yarn playwright codegen Auto generate tests with Codegen. +```yarn playwright codegen``` -Allure report needs JAVA_HOME set +Allure report display needs JAVA_HOME set and to run the server JDK version 8 to 11 otherwise you get +``` Starting web server... Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: /usr/lib/jvm/java-17-openjdk-amd64/lib/libawt_xawt.so at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2398) at java.base/java.lang.Runtime.load0(Runtime.java:755) at java.base/java.lang.System.load(System.java:1970) -at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) - - +at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) +``` diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 109e68c5de..d9d7d0d2f0 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -17,7 +17,7 @@ "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", - "test:allure": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results" - + "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", + "test:autogen": "playwright codegen" } } From a71c200ff595cc8c3fec631be50daba3afa17ee9 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Wed, 12 Mar 2025 10:56:13 +0200 Subject: [PATCH 003/128] move dependencies --- tests/playwright/README.md | 33 +- tests/playwright/helpers/constants.ts | 133 +++++ tests/playwright/helpers/database.ts | 454 ++++++++++++++++++ tests/playwright/package.json | 3 +- .../dialogs/add-rdi-instance-dialog.ts | 37 ++ .../dialogs/add-redis-database-dialog.ts | 392 +++++++++++++++ .../dialogs/authorization-dialog.ts | 9 + .../pageObjects/dialogs/filters-dialog.ts | 57 +++ tests/playwright/pageObjects/dialogs/index.ts | 14 + .../dialogs/onboarding-cards-dialog.ts | 60 +++ .../dialogs/user-agreement-dialog.ts | 36 ++ tests/playwright/playwright.config.ts | 201 ++++---- tests/playwright/tests/addKeys.spec.ts | 101 ++++ 13 files changed, 1429 insertions(+), 101 deletions(-) create mode 100644 tests/playwright/helpers/constants.ts create mode 100644 tests/playwright/helpers/database.ts create mode 100644 tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts create mode 100644 tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts create mode 100644 tests/playwright/pageObjects/dialogs/authorization-dialog.ts create mode 100644 tests/playwright/pageObjects/dialogs/filters-dialog.ts create mode 100644 tests/playwright/pageObjects/dialogs/index.ts create mode 100644 tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts create mode 100644 tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts create mode 100644 tests/playwright/tests/addKeys.spec.ts diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 0cd5bb7cd8..015b14cfbf 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,33 +1,50 @@ ## Install -Install Playwright browsers - 'yarn playwright install' +Install Playwright browsers +``` +yarn playwright install +``` -Install Playwright operating system dependencies requires sudo / root - 'sudo yarn playwright install-deps' +Install Playwright operating system dependencies requires sudo / root +``` +sudo yarn playwright install-deps +``` ## Runs Runs the end-to-end tests. - -```yarn playwright test``` +``` +yarn playwright test +``` Runs the tests only on Desktop Chrome. -```yarn playwright test --project=chromium``` +``` +yarn playwright test --project=localChromium +``` Runs the tests in debug mode. -```yarn playwright test --debug``` +``` +yarn playwright test --debug +``` Runs the tests in a specific file. -```yarn playwright test example``` +``` +yarn playwright test example +``` Starts the interactive UI mode. This also can be set in the config -```yarn playwright test --ui``` +``` +yarn playwright test --ui +``` + +[More info on running tests](https://playwright.dev/docs/running-tests) ## Extra tooling diff --git a/tests/playwright/helpers/constants.ts b/tests/playwright/helpers/constants.ts new file mode 100644 index 0000000000..c63025005c --- /dev/null +++ b/tests/playwright/helpers/constants.ts @@ -0,0 +1,133 @@ +export enum KeyTypesTexts { + Hash = 'Hash', + List = 'List', + Set = 'Set', + ZSet = 'Sorted Set', + String = 'String', + ReJSON = 'JSON', + Stream = 'Stream', + Graph = 'Graph', + TimeSeries = 'Time Series', +} +export const keyLength = 50; + +export const COMMANDS_TO_CREATE_KEY = Object.freeze({ + [KeyTypesTexts.Hash]: (key: string, value: string | number = 'value', field: string | number = 'field') => `HSET ${key} '${field}' '${value}'`, + [KeyTypesTexts.List]: (key: string, element: string | number = 'element') => `LPUSH ${key} '${element}'`, + [KeyTypesTexts.Set]: (key: string, member = 'member') => `SADD ${key} '${member}'`, + [KeyTypesTexts.ZSet]: (key: string, member = 'member', score = 1) => `ZADD ${key} ${score} '${member}'`, + [KeyTypesTexts.String]: (key: string, value = 'val') => `SET ${key} '${value}'`, + [KeyTypesTexts.ReJSON]: (key: string, json = '"val"') => `JSON.SET ${key} . '${json}'`, + [KeyTypesTexts.Stream]: (key: string, value: string | number = 'value', field: string | number = 'field') => `XADD ${key} * '${field}' '${value}'`, + [KeyTypesTexts.Graph]: (key: string) => `GRAPH.QUERY ${key} "CREATE ()"`, + [KeyTypesTexts.TimeSeries]: (key: string) => `TS.CREATE ${key}` +}); + +export enum rte { + none = 'none', + standalone = 'standalone', + sentinel = 'sentinel', + ossCluster = 'oss-cluster', + reCluster = 're-cluster', + reCloud = 're-cloud' +} + +export enum env { + web = 'web', + desktop = 'desktop' +} + +export enum RecommendationIds { + redisVersion = 'redisVersion', + searchVisualization = 'searchVisualization', + setPassword = 'setPassword', + optimizeTimeSeries = 'RTS', + luaScript = 'luaScript', + useSmallerKeys = 'useSmallerKeys', + avoidLogicalDatabases = 'avoidLogicalDatabases', + searchJson = 'searchJSON', + rdi = 'tryRDI' +} + +export enum LibrariesSections { + Functions = 'Functions', + KeyspaceTriggers = 'Keyspace', + ClusterFunctions = 'Cluster', + StreamFunctions= 'Stream', +} + +export enum FunctionsSections { + General = 'General', + Flag = 'Flag', +} + +export enum MonacoEditorInputs { + //add library fields + Code = 'code-value', + Configuration = 'configuration-value', + // added library fields + Library = 'library-code', + LibraryConfiguration = 'library-configuration', +} + +export enum ResourcePath { + Databases = '/databases', + RedisSentinel = '/redis-sentinel', + ClusterDetails = '/cluster-details', + SyncFeatures = '/features/sync', + Rdi = '/rdi' +} + +export enum ExploreTabs { + Tutorials = 'Tutorials', + Tips = 'Tips', +} + +export enum Compatibility { + SearchAndQuery = 'search', + Json = 'json', + TimeSeries = 'time-series' +} + +export enum ChatBotTabs { + General = 'General', + Database = 'Database', +} + +export enum RedisOverviewPage { + DataBase = 'Redis Databases', + Rdi = 'My RDI instances', +} + +export enum TextConnectionSection { + Success = 'success', + Failed = 'failed', +} + +export enum RdiTemplatePipelineType { + Ingest = 'ingest', + WriteBehind = 'write-behind', +} + +export enum RdiTemplateDatabaseType { + SqlServer = 'sql', + Oracle = 'oracle', + MySql = 'mysql', +} + +export enum RdiPopoverOptions { + Server = 'server', + File = 'file', + Pipeline = 'empty', +} + +export enum TlsCertificates { + CA = 'ca', + Client = 'client', +} + +export enum AddElementInList { + Head , + Tail, +} + diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts new file mode 100644 index 0000000000..edc2468ea0 --- /dev/null +++ b/tests/playwright/helpers/database.ts @@ -0,0 +1,454 @@ +// import { Selector, t } from 'testcafe'; +import { + AddNewDatabaseParameters, + SentinelParameters, + OSSClusterParameters +} from '../pageObjects/dialogs/add-redis-database-dialog'; +import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page'; +import { + MyRedisDatabasePage, + BrowserPage, + AutoDiscoverREDatabases +} from '../pageObjects'; +import { UserAgreementDialog } from '../pageObjects/dialogs'; +import { DatabaseAPIRequests } from './api/api-database'; +import { RedisOverviewPage } from './constants'; +import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page'; +import { updateControlNumber } from './insights'; + +const myRedisDatabasePage = new MyRedisDatabasePage(); +const discoverMasterGroupsPage = new DiscoverMasterGroupsPage(); +const autoDiscoverREDatabases = new AutoDiscoverREDatabases(); +const browserPage = new BrowserPage(); +const userAgreementDialog = new UserAgreementDialog(); +const databaseAPIRequests = new DatabaseAPIRequests(); +const rdiInstancesListPage = new RdiInstancesListPage(); + +export class DatabaseHelper { + /** + * Add a new database manually using host and port + * @param databaseParameters The database parameters + */ + async addNewStandaloneDatabase( + databaseParameters: AddNewDatabaseParameters + ): Promise { + // Fill the add database form + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( + databaseParameters + ); + // Click for saving + await t + .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) + // Wait for database to be exist + .expect( + myRedisDatabasePage.dbNameList.withExactText( + databaseParameters.databaseName ?? '' + ).exists + ) + .ok('The database not displayed', { timeout: 10000 }) + // Close message + .click(myRedisDatabasePage.Toast.toastCloseButton); + } + + /** + * Add a new database via autodiscover using Sentinel option + * @param databaseParameters The Sentinel parameters: host, port and sentinel password + */ + async discoverSentinelDatabase( + databaseParameters: SentinelParameters + ): Promise { + // Fill sentinel parameters to auto-discover Master Groups + await myRedisDatabasePage.AddRedisDatabaseDialog.discoverSentinelDatabases( + databaseParameters + ); + // Click for autodiscover + await t + .click( + myRedisDatabasePage.AddRedisDatabaseDialog + .addRedisDatabaseButton + ) + .expect(discoverMasterGroupsPage.addPrimaryGroupButton.exists) + .ok('User is not on the second step of Sentinel flow', { + timeout: 10000 + }); + // Select Master Groups and Add to Redis Insight + await discoverMasterGroupsPage.addMasterGroups(); + await t.click(autoDiscoverREDatabases.viewDatabasesButton); + } + + /** + * Add a new database from RE Cluster via auto-discover flow + * @param databaseParameters The database parameters + */ + async addNewREClusterDatabase( + databaseParameters: AddNewDatabaseParameters + ): Promise { + // Fill the add database form + await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverREClusterDatabase( + databaseParameters + ); + // Click on submit button + await t + .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) + // Wait for database to be exist in the list of Autodiscover databases and select it + .expect( + autoDiscoverREDatabases.databaseName.withExactText( + databaseParameters.databaseName ?? '' + ).exists + ) + .ok('The database not displayed', { timeout: 10000 }) + .typeText( + autoDiscoverREDatabases.search, + databaseParameters.databaseName ?? '' + ) + .click(autoDiscoverREDatabases.databaseCheckbox) + // Click Add selected databases button + .click(autoDiscoverREDatabases.addSelectedDatabases) + .click(autoDiscoverREDatabases.viewDatabasesButton); + } + + /** + * Add a new database from OSS Cluster via auto-discover flow + * @param databaseParameters The database parameters + */ + async addOSSClusterDatabase( + databaseParameters: OSSClusterParameters + ): Promise { + // Enter required parameters for OSS Cluster + await myRedisDatabasePage.AddRedisDatabaseDialog.addOssClusterDatabase( + databaseParameters + ); + // Click for saving + await t + .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) + // Check for info message that DB was added + .expect(myRedisDatabasePage.Toast.toastHeader.exists) + .ok('Info message not exists', { timeout: 10000 }) + // Wait for database to be exist + .expect( + myRedisDatabasePage.dbNameList.withExactText( + databaseParameters.ossClusterDatabaseName + ).exists + ) + .ok('The database not displayed', { timeout: 10000 }); + } + + /** + * Add a new database from Redis Cloud via auto-discover flow + * @param cloudAPIAccessKey The Cloud API Access Key + * @param cloudAPISecretKey The Cloud API Secret Key + */ + async autodiscoverRECloudDatabase( + cloudAPIAccessKey: string, + cloudAPISecretKey: string + ): Promise { + // Fill the add database form and Submit + await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverRECloudDatabase( + cloudAPIAccessKey, + cloudAPISecretKey + ); + await t.click( + myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton + ); + await t + .expect( + autoDiscoverREDatabases.title.withExactText( + 'Redis Cloud Subscriptions' + ).exists + ) + .ok('Subscriptions list not displayed', { timeout: 120000 }); + // Select subscriptions + await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox); + await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton); + // Select databases for adding + const databaseName = await autoDiscoverREDatabases.getDatabaseName(); + await t.click(autoDiscoverREDatabases.databaseCheckbox); + await t.click(autoDiscoverREDatabases.addSelectedDatabases); + // Wait for database to be exist in the redis databases list + await t + .expect( + autoDiscoverREDatabases.title.withExactText( + 'Redis Enterprise Databases Added' + ).exists + ) + .ok('Added databases list not displayed', { timeout: 20000 }); + await t.click(autoDiscoverREDatabases.viewDatabasesButton); + // uncomment when fixed db will be added to cloud subscription + // await t.expect(myRedisDatabasePage.dbNameList.withExactText(databaseName).exists).ok('The database not displayed', { timeout: 10000 }); + return databaseName; + } + + /** + * Accept License terms and add database + * @param databaseParameters The database parameters + * @param databaseName The database name + */ + async acceptLicenseTermsAndAddDatabase( + databaseParameters: AddNewDatabaseParameters + ): Promise { + await this.acceptLicenseTerms(); + await this.addNewStandaloneDatabase(databaseParameters); + // Connect to DB + await myRedisDatabasePage.clickOnDBByName( + databaseParameters.databaseName! + ); + } + + /** + * Accept License terms and add database using api + * @param databaseParameters The database parameters + * @param databaseName The database name + */ + async acceptLicenseTermsAndAddDatabaseApi( + databaseParameters: AddNewDatabaseParameters + ): Promise { + await this.acceptLicenseTerms(); + await databaseAPIRequests.addNewStandaloneDatabaseApi( + databaseParameters + ); + // Reload Page to see the new added database through api + await myRedisDatabasePage.reloadPage(); + // Connect to DB + await myRedisDatabasePage.clickOnDBByName( + databaseParameters.databaseName! + ); + } + + /** + * Accept License terms and add OSS cluster database + * @param databaseParameters The database parameters + * @param databaseName The database name + */ + async acceptLicenseTermsAndAddOSSClusterDatabase( + databaseParameters: OSSClusterParameters + ): Promise { + await this.acceptLicenseTerms(); + await this.addOSSClusterDatabase(databaseParameters); + // Connect to DB + await myRedisDatabasePage.clickOnDBByName( + databaseParameters.ossClusterDatabaseName! + ); + } + + /** + * Accept License terms and add Sentinel database using api + * @param databaseParameters The database parameters + */ + async acceptLicenseTermsAndAddSentinelDatabaseApi( + databaseParameters: SentinelParameters + ): Promise { + await this.acceptLicenseTerms(); + await databaseAPIRequests.discoverSentinelDatabaseApi( + databaseParameters + ); + // Reload Page to see the database added through api + await myRedisDatabasePage.reloadPage(); + // Connect to DB + await myRedisDatabasePage.clickOnDBByName( + databaseParameters.masters![1].alias ?? '' + ); + } + + /** + * Accept License terms and add RE Cluster database + * @param databaseParameters The database parameters + */ + async acceptLicenseTermsAndAddREClusterDatabase( + databaseParameters: AddNewDatabaseParameters + ): Promise { + await this.acceptLicenseTerms(); + await this.addNewREClusterDatabase(databaseParameters); + // Connect to DB + await myRedisDatabasePage.clickOnDBByName( + databaseParameters.databaseName ?? '' + ); + } + + /** + * Accept License terms and add RE Cloud database + * @param databaseParameters The database parameters + */ + async acceptLicenseTermsAndAddRECloudDatabase( + databaseParameters: AddNewDatabaseParameters + ): Promise { + const searchTimeout = 60 * 1000; // 60 sec to wait database appearing + const dbSelector = myRedisDatabasePage.dbNameList.withExactText( + databaseParameters.databaseName ?? '' + ); + const startTime = Date.now(); + + await this.acceptLicenseTerms(); + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( + databaseParameters + ); + // Click for saving + await t.click( + myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton + ); + await t.wait(3000); + // Reload page until db appears + do { + await myRedisDatabasePage.reloadPage(); + } while ( + !(await dbSelector.exists) && + Date.now() - startTime < searchTimeout + ); + await t + .expect( + myRedisDatabasePage.dbNameList.withExactText( + databaseParameters.databaseName ?? '' + ).exists + ) + .ok('The database not displayed', { timeout: 5000 }); + await myRedisDatabasePage.clickOnDBByName( + databaseParameters.databaseName ?? '' + ); + await t + .expect(browserPage.keysSummary.exists) + .ok('Key list not loaded', { timeout: 15000 }); + } + + /** + * Add RE Cloud database + * @param databaseParameters The database parameters + */ + async addRECloudDatabase( + databaseParameters: AddNewDatabaseParameters + ): Promise { + const searchTimeout = 60 * 1000; // 60 sec to wait database appearing + const dbSelector = myRedisDatabasePage.dbNameList.withExactText( + databaseParameters.databaseName ?? '' + ); + const startTime = Date.now(); + + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( + databaseParameters + ); + // Click for saving + await t.click( + myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton + ); + await t.wait(3000); + // Reload page until db appears + do { + await myRedisDatabasePage.reloadPage(); + } while ( + !(await dbSelector.exists) && + Date.now() - startTime < searchTimeout + ); + await t + .expect( + myRedisDatabasePage.dbNameList.withExactText( + databaseParameters.databaseName ?? '' + ).exists + ) + .ok('The database not displayed', { timeout: 5000 }); + } + + // Accept License terms + async acceptLicenseTerms(): Promise { + await t.maximizeWindow(); + await userAgreementDialog.acceptLicenseTerms(); + await updateControlNumber(48.2); + // Open default databases list tab if RDI opened + if (await rdiInstancesListPage.addRdiInstanceButton.exists) { + await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase); + } + // TODO delete after releasing chatbot + if (await myRedisDatabasePage.AddRedisDatabaseDialog.aiChatMessage.exists) { + await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.aiCloseMessage) + } + } + + // Accept License terms and connect to the RedisStack database + async acceptLicenseAndConnectToRedisStack(): Promise { + await this.acceptLicenseTerms(); + //Connect to DB + await t + .click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) + .click( + myRedisDatabasePage.AddRedisDatabaseDialog.connectToRedisStackButton + ); + } + + /** + * Delete database + * @param databaseName The database name + */ + async deleteDatabase(databaseName: string): Promise { + await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton); + if ( + await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists + ) { + await this.deleteDatabaseByNameApi(databaseName); + } + } + + /** + * Delete database with custom name + * @param databaseName The database name + */ + async deleteCustomDatabase(databaseName: string): Promise { + await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton); + if ( + await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists + ) { + await myRedisDatabasePage.deleteDatabaseByName(databaseName); + } + } + + /** + * Accept License terms and add database or connect to the Redis stask database + * @param databaseParameters The database parameters + * @param databaseName The database name + */ + async acceptTermsAddDatabaseOrConnectToRedisStack( + databaseParameters: AddNewDatabaseParameters + ): Promise { + if ( + await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists + ) { + await this.acceptLicenseTermsAndAddDatabase(databaseParameters); + } + else { + await this.acceptLicenseAndConnectToRedisStack(); + } + } + + /** + * Click on the edit database button by name + * @param databaseName The name of the database + */ + async clickOnEditDatabaseByName(databaseName: string): Promise { + const databaseId = await databaseAPIRequests.getDatabaseIdByName( + databaseName + ); + const databaseEditBtn = Selector( + `[data-testid=edit-instance-${databaseId}]` + ); + + await t + .expect(databaseEditBtn.exists) + .ok(`"${databaseName}" database not displayed`); + await t.click(databaseEditBtn); + } + + /** + * Delete database button by name + * @param databaseName The name of the database + */ + async deleteDatabaseByNameApi(databaseName: string): Promise { + const databaseId = await databaseAPIRequests.getDatabaseIdByName( + databaseName + ); + const databaseDeleteBtn = Selector( + `[data-testid=delete-instance-${databaseId}-icon]` + ); + + await t + .expect(databaseDeleteBtn.exists) + .ok(`"${databaseName}" database not displayed`); + await t.click(databaseDeleteBtn); + await t.click(myRedisDatabasePage.confirmDeleteButton); + } +} diff --git a/tests/playwright/package.json b/tests/playwright/package.json index d9d7d0d2f0..10d19891ce 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -11,10 +11,11 @@ "allure-playwright": "^3.2.0" }, "scripts": { + "removeReportDirs":"rm -rf allure-results playwright-report test-results", "allTests": "playwright test", "generateAndShowReports": "allure serve allure-results", "generateReports": "allure generate --clean", - + "test:chromium": "playwright test --project=localChromium", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", diff --git a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts new file mode 100644 index 0000000000..6da010ba70 --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts @@ -0,0 +1,37 @@ +import { expect, type Locator, type Page } from '@playwright/test'; + +export class AddRdiInstanceDialog { + //INPUTS + rdiAliasInput = Selector('[data-testid=connection-form-name-input]'); + urlInput = Selector('[data-testid=connection-form-url-input]'); + usernameInput = Selector('[data-testid=connection-form-username-input]'); + passwordInput = Selector('[data-testid=connection-form-password-input]'); + + //BUTTONS + addInstanceButton = Selector('[data-testid=connection-form-add-button]'); + cancelInstanceBtn = Selector('[data-testid=connection-form-cancel-button]'); + + connectToRdiForm = Selector('[data-testid=connection-form]'); + // ICONS + urlInputInfoIcon = Selector('[data-testid=connection-form-url-input]').parent('div').parent('div').find('svg'); + usernameInputInfoIcon = Selector('[data-testid=connection-form-username-input]').parent('div').parent('div').find('svg'); + passwordInputInfoIcon = Selector('[data-testid=connection-form-password-input]').parent('div').parent('div').find('svg'); +} + +/** + * String key parameters + * @param alias The name of the rdi + * @param url The url for rdi + * @param version The version for rdi + * @param lastConnection The last Connection to the rdi instance + * @param username The username for rdi + * @param password The password for rdi + */ +export type RdiInstance = { + alias: string, + url: string, + version?: string, + lastConnection?: string, + username?: string, + password?: string +}; diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts new file mode 100644 index 0000000000..ffac503bd8 --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts @@ -0,0 +1,392 @@ +import { Selector, t } from 'testcafe'; +import { TlsCertificates } from '../../helpers/constants'; +import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel'; + +export class AddRedisDatabaseDialog { + RedisCloudSigninPanel = new RedisCloudSigninPanel(); + + //------------------------------------------------------------------------------------------- + //DECLARATION OF SELECTORS + //*Declare all elements/components of the relevant page. + //*Target any element/component via data-id, if possible! + //*The following categories are ordered alphabetically (Alerts, Buttons, Checkboxes, etc.). + //------------------------------------------------------------------------------------------- + // BUTTONS + addDatabaseButton = Selector('[data-testid^=add-redis-database]'); + addRedisDatabaseButton = Selector('[data-testid=btn-submit]'); + customSettingsButton = Selector('[data-testid=btn-connection-settings]'); + addAutoDiscoverDatabase = Selector('[data-testid=add-database_tab_software]'); + addCloudDatabaseButton = Selector('[data-testid=create-free-db-btn]'); + redisSotfwareButton = Selector('[data-testid=option-btn-software]'); + redisSentinelButton = Selector('[data-testid=option-btn-sentinel]'); + showDatabasesButton = Selector('[data-testid=btn-show-databases]'); + databaseName = Selector('.euiTableCellContent.column_name'); + selectAllCheckbox = Selector('[data-test-subj=checkboxSelectAll]'); + databaseIndexCheckbox = Selector('[data-testid=showDb]~div', { timeout: 500 }); + connectToRedisStackButton = Selector('[aria-label="Connect to database"]'); + cloneDatabaseButton = Selector('[data-testid=clone-db-btn]'); + cancelButton = Selector('[data-testid=btn-cancel]'); + testConnectionBtn = Selector('[data-testid=btn-test-connection]'); + backButton = Selector('[data-testid=back-btn]'); + generalTab = Selector('[data-testid=manual-form-tab-general]'); + securityTab = Selector('[data-testid=manual-form-tab-security]'); + decompressionTab = Selector('[data-testid=manual-form-tab-decompression]'); + + // TEXT INPUTS (also referred to as 'Text fields') + disabledDatabaseInfo = Selector('[class=euiListGroupItem__label]'); + hostInput = Selector('[data-testid=host]'); + portInput = Selector('[data-testid=port]'); + databaseAliasInput = Selector('[data-testid=name]'); + passwordInput = Selector('[data-testid=password]'); + usernameInput = Selector('[data-testid=username]'); + connectionUrlInput = Selector('[data-testid=connection-url]'); + accessKeyInput = Selector('[data-testid=access-key]'); + secretKeyInput = Selector('[data-testid=secret-key]'); + databaseIndexInput = Selector('[data-testid=db]'); + databaseIndexMessage = Selector('[data-testid=db-index-message]'); + primaryGroupNameInput = Selector('[data-testid=primary-group]'); + masterGroupPassword = Selector('[data-testid=sentinel-master-password]'); + connectionType = Selector('[data-testid=connection-type]'); + sentinelForm = Selector('[data-testid=form]'); + sshHostInput = Selector('[data-testid=sshHost]'); + sshPortInput = Selector('[data-testid=sshPort]'); + sshUsernameInput = Selector('[data-testid=sshUsername]'); + sshPasswordInput = Selector('[data-testid=sshPassword]'); + sshPrivateKeyInput = Selector('[data-testid=sshPrivateKey]'); + sshPassphraseInput = Selector('[data-testid=sshPassphrase]'); + timeoutInput = Selector('[data-testid=timeout]'); + // DROPDOWNS + caCertField = Selector('[data-testid=select-ca-cert]', { timeout: 500 }); + clientCertField = Selector('[data-testid=select-cert]', { timeout: 500 }); + selectCompressor = Selector('[data-testid=select-compressor]', { timeout: 1000 }); + certificateDropdownList = Selector('div.euiSuperSelect__listbox div'); + // CHECKBOXES + useSSHCheckbox = Selector('[data-testid=use-ssh]~div', { timeout: 500 }); + dataCompressorCheckbox = Selector('[data-testid=showCompressor] ~ label'); + requiresTlsClientCheckbox = Selector('[data-testid=tls-required-checkbox] ~ label'); + useCloudAccount = Selector('#cloud-account').parent(); + useCloudKeys = Selector('#cloud-api-keys').parent(); + // RADIO BUTTONS + sshPasswordRadioBtn = Selector('#password~div', { timeout: 500 }); + sshPrivateKeyRadioBtn = Selector('#privateKey~div', { timeout: 500 }); + cloudOptionsRadioBtn = Selector('[data-testid=cloud-options]'); + // LABELS + dataCompressorLabel = Selector('[data-testid=showCompressor] ~ label', { timeout: 1000 }); + aiChatMessage = Selector('[data-testid=ai-chat-message-btn]'); + aiCloseMessage = Selector('[aria-label="Closes this modal window"]'); + + trashIconMsk = (certificate: TlsCertificates) => `[data-testid^=delete-${certificate}-cert]` + + getDeleteCertificate = (certificate: TlsCertificates) => Selector(this.trashIconMsk(certificate)); + + /** + * Adding a new redis database + * @param parameters the parameters of the database + */ + async addRedisDataBase(parameters: AddNewDatabaseParameters): Promise { + + await this.addDatabaseButton.with({ visibilityCheck: true, timeout: 10000 })(); + await t + .click(this.addDatabaseButton) + .click(this.customSettingsButton); + + await t + .typeText(this.hostInput, parameters.host, { replace: true, paste: true }) + .typeText(this.portInput, parameters.port, { replace: true, paste: true }) + .typeText(this.databaseAliasInput, parameters.databaseName!, { replace: true, paste: true }); + if (!!parameters.databaseUsername) { + await t.typeText(this.usernameInput, parameters.databaseUsername, { replace: true, paste: true }); + } + if (!!parameters.databasePassword) { + await t.typeText(this.passwordInput, parameters.databasePassword, { replace: true, paste: true }); + } + } + + /** + * Adding a new redis database with index + * @param parameters the parameters of the database + * @param index the logical index of database + */ + async addLogicalRedisDatabase(parameters: AddNewDatabaseParameters, index: string): Promise { + await t + .click(this.addDatabaseButton) + .click(this.customSettingsButton); + + await t + .typeText(this.hostInput, parameters.host, { replace: true, paste: true }) + .typeText(this.portInput, parameters.port, { replace: true, paste: true }) + .typeText(this.databaseAliasInput, parameters.databaseName!, { replace: true, paste: true }); + if (!!parameters.databaseUsername) { + await t.typeText(this.usernameInput, parameters.databaseUsername, { replace: true, paste: true }); + } + if (!!parameters.databasePassword) { + await t.typeText(this.passwordInput, parameters.databasePassword, { replace: true, paste: true }); + } + // Enter logical index + await t.click(this.databaseIndexCheckbox); + await t.typeText(this.databaseIndexInput, index, { replace: true, paste: true }); + // Click for saving + await t.click(this.addRedisDatabaseButton); + } + + /** + * Adding a new standalone database with SSH + * @param databaseParameters the parameters of the database + * @param sshParameters the parameters of ssh + */ + async addStandaloneSSHDatabase(databaseParameters: AddNewDatabaseParameters, sshParameters: SSHParameters): Promise { + + await t + .click(this.addDatabaseButton) + .click(this.customSettingsButton); + + await t + .typeText(this.hostInput, databaseParameters.host, { replace: true, paste: true }) + .typeText(this.portInput, databaseParameters.port, { replace: true, paste: true }) + .typeText(this.databaseAliasInput, databaseParameters.databaseName!, { replace: true, paste: true }); + if (!!databaseParameters.databaseUsername) { + await t.typeText(this.usernameInput, databaseParameters.databaseUsername, { replace: true, paste: true }); + } + if (!!databaseParameters.databasePassword) { + await t.typeText(this.passwordInput, databaseParameters.databasePassword, { replace: true, paste: true }); + } + // Select SSH Tunnel checkbox + await t.click(this.securityTab); + await t.click(this.useSSHCheckbox); + // Enter SSH fields + await t + .typeText(this.sshHostInput, sshParameters.sshHost, { replace: true, paste: true }) + .typeText(this.sshPortInput, sshParameters.sshPort, { replace: true, paste: true }) + .typeText(this.sshUsernameInput, sshParameters.sshUsername, { replace: true, paste: true }); + if (!!sshParameters.sshPassword) { + await t.typeText(this.sshPasswordInput, sshParameters.sshPassword, { replace: true, paste: true }); + } + if (!!sshParameters.sshPrivateKey) { + await t + .click(this.sshPrivateKeyRadioBtn) + .typeText(this.sshPrivateKeyInput, sshParameters.sshPrivateKey, { replace: true, paste: true }); + } + if (!!sshParameters.sshPassphrase) { + await t + .click(this.sshPrivateKeyRadioBtn) + .typeText(this.sshPrivateKeyInput, sshParameters.sshPrivateKey!, { replace: true, paste: true }) + .typeText(this.sshPassphraseInput, sshParameters.sshPassphrase, { replace: true, paste: true }); + } + // Click for saving + await t.click(this.addRedisDatabaseButton); + } + + /** + * Auto-discover Master Groups from Sentinel + * @param parameters - Parameters of Sentinel: host, port and Sentinel password + */ + async discoverSentinelDatabases(parameters: SentinelParameters): Promise { + + await t + .click(this.addDatabaseButton) + + await t.click(this.redisSentinelButton); + if (!!parameters.sentinelHost) { + await t.typeText(this.hostInput, parameters.sentinelHost, { replace: true, paste: true }); + } + if (!!parameters.sentinelPort) { + await t.typeText(this.portInput, parameters.sentinelPort, { replace: true, paste: true }); + } + if (!!parameters.sentinelPassword) { + await t.typeText(this.passwordInput, parameters.sentinelPassword, { replace: true, paste: true }); + } + } + + /** + * Adding a new database from RE Cluster via auto-discover flow + * @param parameters the parameters of the database + */ + async addAutodiscoverREClusterDatabase(parameters: AddNewDatabaseParameters): Promise { + + await t + .click(this.addDatabaseButton) + + await t.click(this.redisSotfwareButton); + await t + .typeText(this.hostInput, parameters.host, { replace: true, paste: true }) + .typeText(this.portInput, parameters.port, { replace: true, paste: true }) + .typeText(this.usernameInput, parameters.databaseUsername!, { replace: true, paste: true }) + .typeText(this.passwordInput, parameters.databasePassword!, { replace: true, paste: true }); + } + + /** + * Adding a new database from RE Cloud via auto-discover flow + * @param parameters the parameters of the database + */ + async addAutodiscoverRECloudDatabase(cloudAPIAccessKey: string, cloudAPISecretKey: string): Promise { + + await t + .click(this.addDatabaseButton) + .click(this.addCloudDatabaseButton); + + await t + .typeText(this.accessKeyInput, cloudAPIAccessKey, { replace: true, paste: true }) + .typeText(this.secretKeyInput, cloudAPISecretKey, { replace: true, paste: true }); + } + + /** + * Auto-discover Master Groups from Sentinel + * @param parameters - Parameters of Sentinel: host, port and Sentinel password + */ + async addOssClusterDatabase(parameters: OSSClusterParameters): Promise { + + await t + .click(this.addDatabaseButton) + .click(this.customSettingsButton); + + if (!!parameters.ossClusterHost) { + await t.typeText(this.hostInput, parameters.ossClusterHost, { replace: true, paste: true }); + } + if (!!parameters.ossClusterPort) { + await t.typeText(this.portInput, parameters.ossClusterPort, { replace: true, paste: true }); + } + if (!!parameters.ossClusterDatabaseName) { + await t.typeText(this.databaseAliasInput, parameters.ossClusterDatabaseName, { replace: true, paste: true }); + } + } + + /** + * set copressor value in dropdown + * @param compressor - compressor value + */ + async setCompressorValue(compressor: string): Promise { + if(!await this.selectCompressor.exists) { + await t.click(this.dataCompressorLabel); + } + + await t.click(this.selectCompressor); + await t.click(Selector(`[id="${compressor}"]`)); + } + + /** + * Remove certificate + * @param certificate - certificate + * @param name - name of the certificate + */ + async removeCertificateButton(certificate: TlsCertificates, name: string): Promise { + await t.click(this.securityTab); + const row = Selector('button') + .find('div') + .withText(name); + const removeButton = this.trashIconMsk(certificate); + const removeButtonFooter = Selector('[class^=_popoverFooter]'); + + if (certificate === TlsCertificates.CA) { + await t.click(this.caCertField); + } else { + await t.click(this.clientCertField); + } + + await t.click(row.find(removeButton)); + + await t.click(removeButtonFooter.find(removeButton)); + } +} + +/** + * Add new database parameters + * @param host The hostname of the database + * @param port The port of the database + * @param databaseName The name of the database + * @param databaseUsername The username of the database + * @param databasePassword The password of the database + */ +export type AddNewDatabaseParameters = { + host: string, + port: string, + databaseName?: string, + databaseUsername?: string, + databasePassword?: string, + caCert?: { + name?: string, + certificate?: string + }, + clientCert?: { + name?: string, + certificate?: string, + key?: string + } +}; + +/** + * Sentinel database parameters + * @param sentinelHost The host of sentinel + * @param sentinelPort The port of sentinel + * @param sentinelPassword The password of sentinel + */ +export type SentinelParameters = { + sentinelHost: string, + sentinelPort: string, + masters?: { + alias?: string, + db?: string, + name?: string, + password?: string + }[], + sentinelPassword?: string, + name?: string[] +}; + +/** + * OSS Cluster database parameters + * @param ossClusterHost The host of OSS Cluster + * @param ossClusterPort The port of OSS Cluster + * @param ossClusterDatabaseName Database name for OSS Cluster + */ + +export type OSSClusterParameters = { + ossClusterHost: string, + ossClusterPort: string, + ossClusterDatabaseName: string +}; + +/** + * Already existing database parameters + * @param id The id of the database + * @param host The host of the database + * @param port The port of the database + * @param name The name of the database + * @param connectionType The connection type of the database + * @param lastConnection The last connection time of the database + */ +export type databaseParameters = { + id: string, + host?: string, + port?: string, + name?: string, + connectionType?: string, + lastConnection?: string +}; + +/** + * Nodes in OSS Cluster parameters + * @param host The host of the node + * @param port The port of the node + */ +export type ClusterNodes = { + host: string, + port: string +}; + +/** + * SSH parameters + * @param sshHost The hostname of ssh + * @param sshPort The port of ssh + * @param sshUsername The username of ssh + * @param sshPassword The password of ssh + * @param sshPrivateKey The private key of ssh + * @param sshPassphrase The passphrase of ssh + */ +export type SSHParameters = { + sshHost: string, + sshPort: string, + sshUsername: string, + sshPassword?: string, + sshPrivateKey?: string, + sshPassphrase?: string +}; diff --git a/tests/playwright/pageObjects/dialogs/authorization-dialog.ts b/tests/playwright/pageObjects/dialogs/authorization-dialog.ts new file mode 100644 index 0000000000..b472adc4dd --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/authorization-dialog.ts @@ -0,0 +1,9 @@ +import { Selector } from 'testcafe'; +import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel'; + +export class AuthorizationDialog { + RedisCloudSigninPanel = new RedisCloudSigninPanel(); + + //COMPONENTS + authDialog = Selector('[data-testid=social-oauth-dialog]'); +} diff --git a/tests/playwright/pageObjects/dialogs/filters-dialog.ts b/tests/playwright/pageObjects/dialogs/filters-dialog.ts new file mode 100644 index 0000000000..f640a60ad8 --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/filters-dialog.ts @@ -0,0 +1,57 @@ +import { Selector, t } from 'testcafe'; + +export class FiltersDialog { + // INPUTS + delimiterCombobox = Selector('[data-testid=delimiter-combobox]'); + delimiterComboboxInput = Selector('[data-test-subj=comboBoxSearchInput]'); + // BUTTONS + treeViewDelimiterValueCancel = Selector('[data-testid=tree-view-cancel-btn]'); + treeViewDelimiterValueSave = Selector('[data-testid=tree-view-apply-btn]'); + sortingBtn = Selector('[data-testid=tree-view-sorting-select]'); + sortingASCoption = Selector('[id=ASC]'); + sortingDESCoption = Selector('[id=DESC]'); + + /** + * Get Delimiter badge selector by title + * @param delimiterTitle title of the delimiter item + */ + getDelimiterBadgeByTitle(delimiterTitle: string): Selector { + return this.delimiterCombobox.find(`span[title='${delimiterTitle}']`); + } + + /** + * Get Delimiter close button selector by title + * @param delimiterTitle title of the delimiter item + */ + getDelimiterCloseBtnByTitle(delimiterTitle: string): Selector { + return this.getDelimiterBadgeByTitle(delimiterTitle).find('button'); + } + + /** + * Add new delimiter + * @param delimiterName name of the delimiter item + */ + async addDelimiterItem(delimiterName: string): Promise { + await t.click(this.delimiterComboboxInput); + await t.typeText(this.delimiterComboboxInput, delimiterName, { paste: true }) + } + + /** + * Delete existing delimiter + * @param delimiterName name of the delimiter item + */ + async removeDelimiterItem(delimiterName: string): Promise { + await t.click(this.getDelimiterCloseBtnByTitle(delimiterName)); + } + + /** + * Remove all existing delimiters in combobox + */ + async clearDelimiterCombobox(): Promise { + const delimiters = this.delimiterCombobox.find('button'); + const count = await delimiters.count; + for (let i = 0; i < count; i++) { + await t.click(delimiters.nth(i)); + } + } +} diff --git a/tests/playwright/pageObjects/dialogs/index.ts b/tests/playwright/pageObjects/dialogs/index.ts new file mode 100644 index 0000000000..ebee73341f --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/index.ts @@ -0,0 +1,14 @@ +import { AddRedisDatabaseDialog } from './add-redis-database-dialog'; +import { AuthorizationDialog } from './authorization-dialog'; +import { OnboardingCardsDialog } from './onboarding-cards-dialog'; +import { FiltersDialog } from './filters-dialog'; +import { UserAgreementDialog } from './user-agreement-dialog'; + + +export { + AddRedisDatabaseDialog, + AuthorizationDialog, + OnboardingCardsDialog, + FiltersDialog, + UserAgreementDialog +}; diff --git a/tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts b/tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts new file mode 100644 index 0000000000..d7b1d859d7 --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts @@ -0,0 +1,60 @@ +import { Selector, t } from 'testcafe'; + +export class OnboardingCardsDialog { + backButton = Selector('[data-testid=back-btn]'); + nextButton = Selector('[data-testid=next-btn]'); + showMeAroundButton = Selector('span').withText('Show me around'); + skipTourButton = Selector('[data-testid=skip-tour-btn]'); + stepTitle = Selector('[data-testid=step-title]'); + wbOnbardingCommand = Selector('[data-testid=wb-onboarding-command]'); + copyCodeButton = Selector('[data-testid=copy-code-btn]'); + resetOnboardingBtn = Selector('[data-testid=reset-onboarding-btn]'); + + /** + * Verify onboarding step visible based on title + * @param stepName title of the step + */ + async verifyStepVisible(stepName: string): Promise { + await t.expect(this.stepTitle.withText(stepName).exists).ok(`${stepName} step is not visible`); + } + /** + Click next step + */ + async clickNextStep(): Promise { + await t.click(this.nextButton); + } + /** + Click next step until the last step + */ + async clickNextUntilLastStep(): Promise { + do { + await this.clickNextStep(); + } + while (await this.skipTourButton.visible); + } + /** + Start onboarding process + */ + async startOnboarding(): Promise { + await t.click(this.showMeAroundButton); + } + /** + Complete onboarding process + */ + async completeOnboarding(): Promise { + await t.expect(this.showMeAroundButton.exists).notOk('Show me around button still visible'); + await t.expect(this.stepTitle.exists).notOk('Onboarding tooltip still visible'); + } + /** + Click back step + */ + async clickBackStep(): Promise { + await t.click(this.backButton); + } + /** + Click skip tour step + */ + async clickSkipTour(): Promise { + await t.click(this.skipTourButton); + } +} diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts new file mode 100644 index 0000000000..ae248d9029 --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -0,0 +1,36 @@ +import { t, Selector } from 'testcafe'; + +export class UserAgreementDialog { + //------------------------------------------------------------------------------------------- + //DECLARATION OF SELECTORS + //*Declare all elements/components of the relevant page. + //*Target any element/component via data-id, if possible! + //*The following categories are ordered alphabetically (Alerts, Buttons, Checkboxes, etc.). + //------------------------------------------------------------------------------------------- + //COMPONENTS + userAgreementsPopup = Selector('[data-testid=consents-settings-popup]'); + //BUTTONS + submitButton = Selector('[data-testid=btn-submit]'); + switchOptionEula = Selector('[data-testid=switch-option-eula]'); + switchOptionEncryption = Selector('[data-testid=switch-option-encryption]'); + pluginSectionWithText = Selector('[data-testid=plugin-section]'); + recommendedSwitcher = Selector('[data-testid=switch-option-recommended]'); + + //Accept Redis Insight License Terms + async acceptLicenseTerms(): Promise { + if (await this.switchOptionEula.exists) { + await t + .click(this.recommendedSwitcher) + .click(this.switchOptionEula) + .click(this.submitButton) + .expect(this.userAgreementsPopup.exists).notOk('The user agreements popup is not shown', { timeout: 2000 }); + } + } + + /** + * Get state of Recommended switcher + */ + async getRecommendedSwitcherValue(): Promise { + return await this.recommendedSwitcher.getAttribute('aria-checked'); + } +} diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 56f0c988b1..2299ea2fa3 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test'; +import {defineConfig, devices} from '@playwright/test'; import * as os from "node:os"; import {Status} from "allure-js-commons"; @@ -14,102 +14,119 @@ import {Status} from "allure-js-commons"; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './tests', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [["line"], ["html"], [ - "allure-playwright", - { - resultsDir: "allure-results", - detail: true, - suiteTitle: true, - links: { - issue: { - nameTemplate: "Issue #%s", - urlTemplate: "https://issues.example.com/%s", - }, - tms: { - nameTemplate: "TMS #%s", - urlTemplate: "https://tms.example.com/%s", - }, - jira: { - urlTemplate: (v: any) => `https://jira.example.com/browse/${v}`, - }, - }, - categories: [ - { - name: "foo", - messageRegex: "bar", - traceRegex: "baz", - matchedStatuses: [Status.FAILED, Status.BROKEN], - }, - ], - environmentInfo: { - os_platform: os.platform(), - os_release: os.release(), - os_version: os.version(), - node_version: process.version, - }, - }, - ]], - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [["line"], ["html"], [ + "allure-playwright", + { + resultsDir: "allure-results", + detail: true, + suiteTitle: true, + links: { + issue: { + nameTemplate: "Issue #%s", + urlTemplate: "https://issues.example.com/%s", + }, + tms: { + nameTemplate: "TMS #%s", + urlTemplate: "https://tms.example.com/%s", + }, + jira: { + urlTemplate: (v: any) => `https://jira.example.com/browse/${v}`, + }, + }, + categories: [ + { + name: "foo", + messageRegex: "bar", + traceRegex: "baz", + matchedStatuses: [Status.FAILED, Status.BROKEN], + }, + ], + environmentInfo: { + os_platform: os.platform(), + os_release: os.release(), + os_version: os.version(), + node_version: process.version, + }, + }, + ]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + testIdAttribute: 'data-testid', + video: { + mode: "on", + size: {width: 1920, height: 1080} + }, }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, + /* Configure projects for major browsers */ + projects: [ + { + name: 'localChromium', + use: { + ...devices['Desktop Chrome'], + baseURL: 'https://chrome.desktop/', + headless: false, + deviceScaleFactor: undefined, + viewport: null, + launchOptions:{ + args: ["--start-maximized", + "--disable-component-extensions-with-background-pages", + "--disable-dev-shm-usage", + "--disable-blink-features=AutomationControlled" + ]} + }, + }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, }); diff --git a/tests/playwright/tests/addKeys.spec.ts b/tests/playwright/tests/addKeys.spec.ts new file mode 100644 index 0000000000..b97cce2e4d --- /dev/null +++ b/tests/playwright/tests/addKeys.spec.ts @@ -0,0 +1,101 @@ +import { rte } from '../helpers/constants'; +import { DatabaseHelper } from '../../../../helpers/database'; +import { BrowserPage } from '../../../../pageObjects'; +import { commonUrl, ossStandaloneConfig } from '../../../../helpers/conf'; +import { Common } from '../../../../helpers/common'; +import { DatabaseAPIRequests } from '../../../../helpers/api/api-database'; +import { APIKeyRequests } from '../../../../helpers/api/api-keys'; +// +// const browserPage = new BrowserPage(); +// const databaseHelper = new DatabaseHelper(); +// const databaseAPIRequests = new DatabaseAPIRequests(); +// const apiKeyRequests = new APIKeyRequests(); +// +// let keyName = Common.generateWord(10); +// +// fixture `Add keys` +// .meta({ type: 'smoke', rte: rte.standalone }) +// .page(commonUrl) +// .beforeEach(async() => { +// await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig); +// }) +// .afterEach(async() => { +// // Clear and delete database +// await apiKeyRequests.deleteKeyByNameApi(keyName, ossStandaloneConfig.databaseName); +// await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); +// }); +// test('Verify that user can add Hash Key', async t => { +// keyName = Common.generateWord(10); +// // Add Hash key +// await browserPage.addHashKey(keyName); +// // Check the notification message +// const notification = browserPage.Toast.toastHeader.textContent; +// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); +// // Check that new key is displayed in the list +// await browserPage.searchByKeyName(keyName); +// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); +// await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); +// }); +// test('Verify that user can add Set Key', async t => { +// keyName = Common.generateWord(10); +// // Add Set key +// await browserPage.addSetKey(keyName); +// // Check the notification message +// const notification = browserPage.Toast.toastHeader.textContent; +// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); +// // Check that new key is displayed in the list +// await browserPage.searchByKeyName(keyName); +// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); +// await t.expect(isKeyIsDisplayedInTheList).ok('The Set key is not added'); +// }); +// test('Verify that user can add List Key', async t => { +// keyName = Common.generateWord(10); +// // Add List key +// await browserPage.addListKey(keyName); +// // Check the notification message +// const notification = browserPage.Toast.toastHeader.textContent; +// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); +// // Check that new key is displayed in the list +// await browserPage.searchByKeyName(keyName); +// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); +// await t.expect(isKeyIsDisplayedInTheList).ok('The List key is not added'); +// }); +// test('Verify that user can add String Key', async t => { +// keyName = Common.generateWord(10); +// // Add String key +// await browserPage.addStringKey(keyName); +// // Check the notification message +// const notification = browserPage.Toast.toastHeader.textContent; +// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); +// // Check that new key is displayed in the list +// await browserPage.searchByKeyName(keyName); +// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); +// await t.expect(isKeyIsDisplayedInTheList).ok('The String key is not added'); +// }); +// test('Verify that user can add ZSet Key', async t => { +// keyName = Common.generateWord(10); +// // Add ZSet key +// await browserPage.addZSetKey(keyName, '111'); +// // Check the notification message +// const notification = browserPage.Toast.toastHeader.textContent; +// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); +// // Check that new key is displayed in the list +// await browserPage.searchByKeyName(keyName); +// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); +// await t.expect(isKeyIsDisplayedInTheList).ok('The ZSet key is not added'); +// }); +// test('Verify that user can add JSON Key', async t => { +// keyName = Common.generateWord(10); +// const keyTTL = '2147476121'; +// const value = '{"name":"xyz"}'; +// +// // Add JSON key +// await browserPage.addJsonKey(keyName, value, keyTTL); +// // Check the notification message +// const notification = browserPage.Toast.toastHeader.textContent; +// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); +// // Check that new key is displayed in the list +// await browserPage.searchByKeyName(keyName); +// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); +// await t.expect(isKeyIsDisplayedInTheList).ok('The JSON key is not added'); +// }); From 4aeb934c2714debef3c81c56dde20c4889ac5289 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Wed, 12 Mar 2025 13:18:57 +0200 Subject: [PATCH 004/128] test commented --- tests/playwright/package.json | 2 +- tests/playwright/tests/addKeys.spec.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 10d19891ce..1a74e0df45 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -13,12 +13,12 @@ "scripts": { "removeReportDirs":"rm -rf allure-results playwright-report test-results", "allTests": "playwright test", - "generateAndShowReports": "allure serve allure-results", "generateReports": "allure generate --clean", "test:chromium": "playwright test --project=localChromium", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", + "generateAndShowReports": "allure serve allure-results", "test:autogen": "playwright codegen" } } diff --git a/tests/playwright/tests/addKeys.spec.ts b/tests/playwright/tests/addKeys.spec.ts index b97cce2e4d..d6b1a6d613 100644 --- a/tests/playwright/tests/addKeys.spec.ts +++ b/tests/playwright/tests/addKeys.spec.ts @@ -1,10 +1,10 @@ -import { rte } from '../helpers/constants'; -import { DatabaseHelper } from '../../../../helpers/database'; -import { BrowserPage } from '../../../../pageObjects'; -import { commonUrl, ossStandaloneConfig } from '../../../../helpers/conf'; -import { Common } from '../../../../helpers/common'; -import { DatabaseAPIRequests } from '../../../../helpers/api/api-database'; -import { APIKeyRequests } from '../../../../helpers/api/api-keys'; +// import { rte } from '../helpers/constants'; +// import { DatabaseHelper } from '../../../../helpers/database'; +// import { BrowserPage } from '../../../../pageObjects'; +// import { commonUrl, ossStandaloneConfig } from '../../../../helpers/conf'; +// import { Common } from '../../../../helpers/common'; +// import { DatabaseAPIRequests } from '../../../../helpers/api/api-database'; +// import { APIKeyRequests } from '../../../../helpers/api/api-keys'; // // const browserPage = new BrowserPage(); // const databaseHelper = new DatabaseHelper(); From 3ad1cf6f0fead3ef716799ca78a29a96f62bfd9c Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 14 Mar 2025 15:14:53 +0200 Subject: [PATCH 005/128] faker + common +consts added --- tests/playwright/.env.localChromium | 3 + tests/playwright/helpers/common.ts | 299 +++++++++++++ tests/playwright/helpers/constants.ts | 6 +- tests/playwright/package.json | 7 +- tests/playwright/pageObjects/base-page.ts | 29 ++ .../dialogs/add-rdi-instance-dialog.ts | 37 -- .../dialogs/add-redis-database-dialog.ts | 392 ------------------ .../dialogs/authorization-dialog.ts | 9 - .../pageObjects/dialogs/filters-dialog.ts | 57 --- tests/playwright/pageObjects/dialogs/index.ts | 14 - .../dialogs/onboarding-cards-dialog.ts | 60 --- .../dialogs/user-agreement-dialog.ts | 36 -- tests/playwright/playwright.config.ts | 47 ++- tests/playwright/tests/addKeys.spec.ts | 55 +-- tests/playwright/tests/example.spec.ts | 36 +- tests/playwright/yarn.lock | 68 +++ 16 files changed, 482 insertions(+), 673 deletions(-) create mode 100644 tests/playwright/.env.localChromium create mode 100644 tests/playwright/helpers/common.ts create mode 100644 tests/playwright/pageObjects/base-page.ts delete mode 100644 tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts delete mode 100644 tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts delete mode 100644 tests/playwright/pageObjects/dialogs/authorization-dialog.ts delete mode 100644 tests/playwright/pageObjects/dialogs/filters-dialog.ts delete mode 100644 tests/playwright/pageObjects/dialogs/index.ts delete mode 100644 tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts delete mode 100644 tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts diff --git a/tests/playwright/.env.localChromium b/tests/playwright/.env.localChromium new file mode 100644 index 0000000000..84139eb53f --- /dev/null +++ b/tests/playwright/.env.localChromium @@ -0,0 +1,3 @@ +BASE_URL=https://chrome.1111 +USERNAME=chromeUser +PASSWORD=chromePass diff --git a/tests/playwright/helpers/common.ts b/tests/playwright/helpers/common.ts new file mode 100644 index 0000000000..64410dbd6c --- /dev/null +++ b/tests/playwright/helpers/common.ts @@ -0,0 +1,299 @@ +import { ClientFunction, RequestMock, t } from 'testcafe' + +import * as path from 'path' +import * as fs from 'fs' +import * as fsp from 'fs/promises' +import { faker } from '@faker-js/faker' +import { apiUrl } from './conf' + +// const archiver = require('archiver') +// +// +// +// declare global { +// interface Window { +// windowId?: string +// } +// } +// +// const settingsApiUrl = `${apiUrl}/settings` +// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' // lgtm[js/disabling-certificate-validation] +// const mockedSettingsResponse = { +// 'theme': null, +// 'dateFormat': null, +// 'timezone': null, +// 'scanThreshold': 10000, +// 'batchSize': 5, +// 'agreements': null +// } +// +export class Common { +// static mockSettingsResponse(): RequestMock { +// return RequestMock() +// .onRequestTo(settingsApiUrl) +// .respond(mockedSettingsResponse, 200, { +// 'Access-Control-Allow-Origin': '*', +// 'Access-Control-Allow-Credentials': 'true', +// 'Access-Control-Allow-Headers': 'x-window-id' +// }) +// } +// +// static async waitForElementNotVisible(elm: Selector): Promise { +// await t.expect(elm.exists).notOk({ timeout: 10000 }) +// } +// +// /** +// * Create array of keys +// * @param length The amount of array elements +// */ +// static createArrayWithKeys(length: number): string[] { +// return Array.from({ length }, (_, i) => `key${i}`) +// } +// +// /** +// * Create array of keys and values +// * @param length The amount of array elements +// */ +// static async createArrayWithKeyValue(length: number): Promise { +// const arr: string[] = [] +// for (let i = 1; i <= length * 2; i++) { +// arr[i] = `${chance.word({ length: 10 })}-key${i}` +// arr[i + 1] = `${chance.word({ length: 10 })}-value${i}` +// i++ +// } +// return arr +// } +// +// /** +// * Create array of keys and values +// * @param length The amount of array elements +// */ +// static async createArrayWithKeyValueAndDelimiter(length: number): Promise { +// const keyNameArray: string[] = [] +// for (let i = 1; i <= length; i++) { +// const key = `"key${i}:test${i}"` +// const value = `"value${this.generateSentence(i * 2)}"` +// keyNameArray.push(key, value) +// } +// return keyNameArray +// } +// +// /** +// * Create array of keys and values +// * @param length The amount of array elements +// */ +// static async createArrayWithKeyAndDelimiter(length: number): Promise { +// const keyNameArray: string[] = [] +// for (let i = 1; i <= length; i++) { +// const key = `"key${i}:test${i}"` +// keyNameArray.push(key) +// } +// return keyNameArray +// } +// +// /** +// * Create array of keys and values for using in OSS Cluster +// * @param length The amount of array elements +// */ +// static async createArrayWithKeyValueForOSSCluster(length: number): Promise { +// const arr: string[] = [] +// for (let i = 1; i <= length * 2; i++) { +// arr[i] = `{user1}:${chance.word({ length: 10 })}-key${i}` +// arr[i + 1] = `${chance.word({ length: 10 })}-value${i}` +// i++ +// } +// return arr +// } +// +// /** +// * Create array of keys and values with edittable counter value +// * @param length The amount of array elements +// * @param keyName The name of the key +// */ +// static async createArrayWithKeyValueAndKeyname(length: number, keyName: string): Promise { +// const keyNameArray: string[] = [] +// for (let i = 1; i <= length; i++) { +// const key = `${keyName}${i}` +// const value = `value${i}` +// keyNameArray.push(key, value) +// } +// return keyNameArray +// } +// +// /** +// * Create array of pairs [key, value] +// * @param length The amount of array elements +// */ +// static createArrayPairsWithKeyValue(length: number): [string, number][] { +// return Array.from({ length }, (_, i) => [`key${i}`, i]) +// } +// +// /** +// * Create array of numbers +// * @param length The amount of array elements +// */ +// static async createArray(length: number): Promise { +// const arr: string[] = [] +// for (let i = 1; i <= length; i++) { +// arr[i] = `${i}` +// } +// return arr +// } +// +// /** +// * Get background colour of element +// * @param element The selector of the element +// */ +// static async getBackgroundColour(element: Selector): Promise { +// return element.getStyleProperty('background-color') +// } +// +// /** +// * Generate word by number of symbols +// * @param number The number of symbols +// */ + static generateWord(number: number): string { + return faker.word.sample({ length: number }) + } + + // /** + // * Generate sentence by number of words + // * @param number The number of words + // */ + // static generateSentence(number: number): string { + // return chance.sentence({ words: number }) + // } + // + // /** + // * Return api endpoint with disabled certificate validation + // */ + // static getEndpoint(): string { + // return apiUrl + // } + // + // /** + // * Return windowId + // */ + // static getWindowId(): Promise { + // return t.eval(() => window.windowId) + // } + // + // /** + // * Check opened URL + // * @param expectedUrl Expected link that is compared with actual + // */ + // static async checkURL(expectedUrl: string): Promise { + // const getPageUrl = await this.getPageUrl() + // await t.expect(getPageUrl).eql(expectedUrl, 'Opened URL is not correct') + // } + // + // /** + // * Check opened URL contains text + // * @param expectedText Expected link that is compared with actual + // */ + // static async checkURLContainsText(expectedText: string): Promise { + // const getPageUrl = await this.getPageUrl() + // await t.expect(getPageUrl).contains(expectedText, `Opened URL not contains text ${expectedText}`) + // } + // + // /** + // * Replace spaces and line breaks + // * @param text text to be replaced + // */ + // static async removeEmptySpacesAndBreak(text: string): Promise { + // return text + // .replace(/ /g, '') + // .replace(/\n/g, '') + // } + // + // /** + // * Get current page url + // */ + // static async getPageUrl(): Promise { + // return (ClientFunction(() => window.location.href))() + // } + // + // /** + // * generate url base on params to create DB + // * @param params params for creating DB + // */ + // static generateUrlTParams(params: Record): string { + // return new URLSearchParams(params).toString() + // } + // + // /** + // * Get json property value by property name and path + // * @param expectedText Expected link that is compared with actual + // */ + // static async getJsonPropertyValue(property: string, path: string): Promise { + // const parsedJson = JSON.parse(fs.readFileSync(path, 'utf-8')) + // return parsedJson[property] + // } + // + // /** + // * Create Zip archive from folder + // * @param folderPath Path to folder to archive + // * @param zipName Zip archive name + // */ + // static async createZipFromFolder(folderPath: string, zipName: string): Promise { + // const sourceDir = path.join(__dirname, folderPath) + // const zipFilePath = path.join(__dirname, zipName) + // const output = fs.createWriteStream(zipFilePath) + // const archive = archiver('zip', { zlib: { level: 9 } }) + // + // // Add the contents of the directory to the zip archive + // archive.directory(sourceDir, false) + // // Finalize the archive and write it to disk + // await archive.finalize() + // archive.pipe(output) + // } + // + // /** + // * Delete file from folder + // * @param filePath Path to file + // */ + // static async deleteFileFromFolder(filePath: string): Promise { + // fs.unlinkSync(path.join(__dirname, filePath)) + // } + // + // /** + // * Delete file from folder if exists + // * @param filePath Path to file + // */ + // static async deleteFileFromFolderIfExists(filePath: string): Promise { + // if (fs.existsSync(filePath)) { + // fs.unlinkSync(filePath) + // } + // } + // + // /** + // * Delete folder + // * @param filePath Path to file + // */ + // static async deleteFolderIfExists(filePath: string): Promise { + // try { + // await fsp.rm(filePath, { recursive: true, force: true }) + // console.log(`Directory Deleted: ${filePath}`) + // } catch (error) { + // console.error(`Failed to delete directory: ${filePath}`, error) + // } + // } + // + // /** + // * Read file from folder + // * @param filePath Path to file + // */ + // static async readFileFromFolder(filePath: string): Promise { + // return fs.readFileSync(filePath, 'utf8') + // } + // + // /** + // * Get current machine platform + // */ + // static getPlatform(): { isMac: boolean, isLinux: boolean } { + // return { + // isMac: process.platform === 'darwin', + // isLinux: process.platform === 'linux' + // } + // } +} diff --git a/tests/playwright/helpers/constants.ts b/tests/playwright/helpers/constants.ts index c63025005c..2d88f5c452 100644 --- a/tests/playwright/helpers/constants.ts +++ b/tests/playwright/helpers/constants.ts @@ -9,7 +9,7 @@ export enum KeyTypesTexts { Graph = 'Graph', TimeSeries = 'Time Series', } -export const keyLength = 50; +export const keyLength = 50 export const COMMANDS_TO_CREATE_KEY = Object.freeze({ [KeyTypesTexts.Hash]: (key: string, value: string | number = 'value', field: string | number = 'field') => `HSET ${key} '${field}' '${value}'`, @@ -21,7 +21,7 @@ export const COMMANDS_TO_CREATE_KEY = Object.freeze({ [KeyTypesTexts.Stream]: (key: string, value: string | number = 'value', field: string | number = 'field') => `XADD ${key} * '${field}' '${value}'`, [KeyTypesTexts.Graph]: (key: string) => `GRAPH.QUERY ${key} "CREATE ()"`, [KeyTypesTexts.TimeSeries]: (key: string) => `TS.CREATE ${key}` -}); +}) export enum rte { none = 'none', @@ -62,7 +62,7 @@ export enum FunctionsSections { } export enum MonacoEditorInputs { - //add library fields + // add library fields Code = 'code-value', Configuration = 'configuration-value', // added library fields diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 1a74e0df45..5fbb953811 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -4,6 +4,7 @@ "main": "index.js", "license": "MIT", "devDependencies": { + "@faker-js/faker": "^9.6.0", "@playwright/test": "^1.51.0", "@types/node": "^22.13.10", "allure-commandline": "^2.33.0", @@ -11,7 +12,7 @@ "allure-playwright": "^3.2.0" }, "scripts": { - "removeReportDirs":"rm -rf allure-results playwright-report test-results", + "removeReportDirs": "rm -rf allure-results playwright-report test-results", "allTests": "playwright test", "generateReports": "allure generate --clean", "test:chromium": "playwright test --project=localChromium", @@ -20,5 +21,9 @@ "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", "generateAndShowReports": "allure serve allure-results", "test:autogen": "playwright codegen" + }, + "dependencies": { + "dotenv": "^16.4.7", + "dotenv-cli": "^8.0.0" } } diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts new file mode 100644 index 0000000000..53ece44815 --- /dev/null +++ b/tests/playwright/pageObjects/base-page.ts @@ -0,0 +1,29 @@ +import { Page } from '@playwright/test' + +export default class BasePage { + protected page: Page + + constructor(page: Page) { + this.page = page + } + + async navigateTo(url: string): Promise { + await this.page.goto(url) + } + + async click(selector: string): Promise { + await this.page.click(selector) + } + + async fill(selector: string, value: string): Promise { + await this.page.fill(selector, value) + } + + async getText(selector: string): Promise { + return await this.page.textContent(selector) || '' + } + + async isVisible(selector: string): Promise { + return this.page.isVisible(selector) + } +} diff --git a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts deleted file mode 100644 index 6da010ba70..0000000000 --- a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { expect, type Locator, type Page } from '@playwright/test'; - -export class AddRdiInstanceDialog { - //INPUTS - rdiAliasInput = Selector('[data-testid=connection-form-name-input]'); - urlInput = Selector('[data-testid=connection-form-url-input]'); - usernameInput = Selector('[data-testid=connection-form-username-input]'); - passwordInput = Selector('[data-testid=connection-form-password-input]'); - - //BUTTONS - addInstanceButton = Selector('[data-testid=connection-form-add-button]'); - cancelInstanceBtn = Selector('[data-testid=connection-form-cancel-button]'); - - connectToRdiForm = Selector('[data-testid=connection-form]'); - // ICONS - urlInputInfoIcon = Selector('[data-testid=connection-form-url-input]').parent('div').parent('div').find('svg'); - usernameInputInfoIcon = Selector('[data-testid=connection-form-username-input]').parent('div').parent('div').find('svg'); - passwordInputInfoIcon = Selector('[data-testid=connection-form-password-input]').parent('div').parent('div').find('svg'); -} - -/** - * String key parameters - * @param alias The name of the rdi - * @param url The url for rdi - * @param version The version for rdi - * @param lastConnection The last Connection to the rdi instance - * @param username The username for rdi - * @param password The password for rdi - */ -export type RdiInstance = { - alias: string, - url: string, - version?: string, - lastConnection?: string, - username?: string, - password?: string -}; diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts deleted file mode 100644 index ffac503bd8..0000000000 --- a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { Selector, t } from 'testcafe'; -import { TlsCertificates } from '../../helpers/constants'; -import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel'; - -export class AddRedisDatabaseDialog { - RedisCloudSigninPanel = new RedisCloudSigninPanel(); - - //------------------------------------------------------------------------------------------- - //DECLARATION OF SELECTORS - //*Declare all elements/components of the relevant page. - //*Target any element/component via data-id, if possible! - //*The following categories are ordered alphabetically (Alerts, Buttons, Checkboxes, etc.). - //------------------------------------------------------------------------------------------- - // BUTTONS - addDatabaseButton = Selector('[data-testid^=add-redis-database]'); - addRedisDatabaseButton = Selector('[data-testid=btn-submit]'); - customSettingsButton = Selector('[data-testid=btn-connection-settings]'); - addAutoDiscoverDatabase = Selector('[data-testid=add-database_tab_software]'); - addCloudDatabaseButton = Selector('[data-testid=create-free-db-btn]'); - redisSotfwareButton = Selector('[data-testid=option-btn-software]'); - redisSentinelButton = Selector('[data-testid=option-btn-sentinel]'); - showDatabasesButton = Selector('[data-testid=btn-show-databases]'); - databaseName = Selector('.euiTableCellContent.column_name'); - selectAllCheckbox = Selector('[data-test-subj=checkboxSelectAll]'); - databaseIndexCheckbox = Selector('[data-testid=showDb]~div', { timeout: 500 }); - connectToRedisStackButton = Selector('[aria-label="Connect to database"]'); - cloneDatabaseButton = Selector('[data-testid=clone-db-btn]'); - cancelButton = Selector('[data-testid=btn-cancel]'); - testConnectionBtn = Selector('[data-testid=btn-test-connection]'); - backButton = Selector('[data-testid=back-btn]'); - generalTab = Selector('[data-testid=manual-form-tab-general]'); - securityTab = Selector('[data-testid=manual-form-tab-security]'); - decompressionTab = Selector('[data-testid=manual-form-tab-decompression]'); - - // TEXT INPUTS (also referred to as 'Text fields') - disabledDatabaseInfo = Selector('[class=euiListGroupItem__label]'); - hostInput = Selector('[data-testid=host]'); - portInput = Selector('[data-testid=port]'); - databaseAliasInput = Selector('[data-testid=name]'); - passwordInput = Selector('[data-testid=password]'); - usernameInput = Selector('[data-testid=username]'); - connectionUrlInput = Selector('[data-testid=connection-url]'); - accessKeyInput = Selector('[data-testid=access-key]'); - secretKeyInput = Selector('[data-testid=secret-key]'); - databaseIndexInput = Selector('[data-testid=db]'); - databaseIndexMessage = Selector('[data-testid=db-index-message]'); - primaryGroupNameInput = Selector('[data-testid=primary-group]'); - masterGroupPassword = Selector('[data-testid=sentinel-master-password]'); - connectionType = Selector('[data-testid=connection-type]'); - sentinelForm = Selector('[data-testid=form]'); - sshHostInput = Selector('[data-testid=sshHost]'); - sshPortInput = Selector('[data-testid=sshPort]'); - sshUsernameInput = Selector('[data-testid=sshUsername]'); - sshPasswordInput = Selector('[data-testid=sshPassword]'); - sshPrivateKeyInput = Selector('[data-testid=sshPrivateKey]'); - sshPassphraseInput = Selector('[data-testid=sshPassphrase]'); - timeoutInput = Selector('[data-testid=timeout]'); - // DROPDOWNS - caCertField = Selector('[data-testid=select-ca-cert]', { timeout: 500 }); - clientCertField = Selector('[data-testid=select-cert]', { timeout: 500 }); - selectCompressor = Selector('[data-testid=select-compressor]', { timeout: 1000 }); - certificateDropdownList = Selector('div.euiSuperSelect__listbox div'); - // CHECKBOXES - useSSHCheckbox = Selector('[data-testid=use-ssh]~div', { timeout: 500 }); - dataCompressorCheckbox = Selector('[data-testid=showCompressor] ~ label'); - requiresTlsClientCheckbox = Selector('[data-testid=tls-required-checkbox] ~ label'); - useCloudAccount = Selector('#cloud-account').parent(); - useCloudKeys = Selector('#cloud-api-keys').parent(); - // RADIO BUTTONS - sshPasswordRadioBtn = Selector('#password~div', { timeout: 500 }); - sshPrivateKeyRadioBtn = Selector('#privateKey~div', { timeout: 500 }); - cloudOptionsRadioBtn = Selector('[data-testid=cloud-options]'); - // LABELS - dataCompressorLabel = Selector('[data-testid=showCompressor] ~ label', { timeout: 1000 }); - aiChatMessage = Selector('[data-testid=ai-chat-message-btn]'); - aiCloseMessage = Selector('[aria-label="Closes this modal window"]'); - - trashIconMsk = (certificate: TlsCertificates) => `[data-testid^=delete-${certificate}-cert]` - - getDeleteCertificate = (certificate: TlsCertificates) => Selector(this.trashIconMsk(certificate)); - - /** - * Adding a new redis database - * @param parameters the parameters of the database - */ - async addRedisDataBase(parameters: AddNewDatabaseParameters): Promise { - - await this.addDatabaseButton.with({ visibilityCheck: true, timeout: 10000 })(); - await t - .click(this.addDatabaseButton) - .click(this.customSettingsButton); - - await t - .typeText(this.hostInput, parameters.host, { replace: true, paste: true }) - .typeText(this.portInput, parameters.port, { replace: true, paste: true }) - .typeText(this.databaseAliasInput, parameters.databaseName!, { replace: true, paste: true }); - if (!!parameters.databaseUsername) { - await t.typeText(this.usernameInput, parameters.databaseUsername, { replace: true, paste: true }); - } - if (!!parameters.databasePassword) { - await t.typeText(this.passwordInput, parameters.databasePassword, { replace: true, paste: true }); - } - } - - /** - * Adding a new redis database with index - * @param parameters the parameters of the database - * @param index the logical index of database - */ - async addLogicalRedisDatabase(parameters: AddNewDatabaseParameters, index: string): Promise { - await t - .click(this.addDatabaseButton) - .click(this.customSettingsButton); - - await t - .typeText(this.hostInput, parameters.host, { replace: true, paste: true }) - .typeText(this.portInput, parameters.port, { replace: true, paste: true }) - .typeText(this.databaseAliasInput, parameters.databaseName!, { replace: true, paste: true }); - if (!!parameters.databaseUsername) { - await t.typeText(this.usernameInput, parameters.databaseUsername, { replace: true, paste: true }); - } - if (!!parameters.databasePassword) { - await t.typeText(this.passwordInput, parameters.databasePassword, { replace: true, paste: true }); - } - // Enter logical index - await t.click(this.databaseIndexCheckbox); - await t.typeText(this.databaseIndexInput, index, { replace: true, paste: true }); - // Click for saving - await t.click(this.addRedisDatabaseButton); - } - - /** - * Adding a new standalone database with SSH - * @param databaseParameters the parameters of the database - * @param sshParameters the parameters of ssh - */ - async addStandaloneSSHDatabase(databaseParameters: AddNewDatabaseParameters, sshParameters: SSHParameters): Promise { - - await t - .click(this.addDatabaseButton) - .click(this.customSettingsButton); - - await t - .typeText(this.hostInput, databaseParameters.host, { replace: true, paste: true }) - .typeText(this.portInput, databaseParameters.port, { replace: true, paste: true }) - .typeText(this.databaseAliasInput, databaseParameters.databaseName!, { replace: true, paste: true }); - if (!!databaseParameters.databaseUsername) { - await t.typeText(this.usernameInput, databaseParameters.databaseUsername, { replace: true, paste: true }); - } - if (!!databaseParameters.databasePassword) { - await t.typeText(this.passwordInput, databaseParameters.databasePassword, { replace: true, paste: true }); - } - // Select SSH Tunnel checkbox - await t.click(this.securityTab); - await t.click(this.useSSHCheckbox); - // Enter SSH fields - await t - .typeText(this.sshHostInput, sshParameters.sshHost, { replace: true, paste: true }) - .typeText(this.sshPortInput, sshParameters.sshPort, { replace: true, paste: true }) - .typeText(this.sshUsernameInput, sshParameters.sshUsername, { replace: true, paste: true }); - if (!!sshParameters.sshPassword) { - await t.typeText(this.sshPasswordInput, sshParameters.sshPassword, { replace: true, paste: true }); - } - if (!!sshParameters.sshPrivateKey) { - await t - .click(this.sshPrivateKeyRadioBtn) - .typeText(this.sshPrivateKeyInput, sshParameters.sshPrivateKey, { replace: true, paste: true }); - } - if (!!sshParameters.sshPassphrase) { - await t - .click(this.sshPrivateKeyRadioBtn) - .typeText(this.sshPrivateKeyInput, sshParameters.sshPrivateKey!, { replace: true, paste: true }) - .typeText(this.sshPassphraseInput, sshParameters.sshPassphrase, { replace: true, paste: true }); - } - // Click for saving - await t.click(this.addRedisDatabaseButton); - } - - /** - * Auto-discover Master Groups from Sentinel - * @param parameters - Parameters of Sentinel: host, port and Sentinel password - */ - async discoverSentinelDatabases(parameters: SentinelParameters): Promise { - - await t - .click(this.addDatabaseButton) - - await t.click(this.redisSentinelButton); - if (!!parameters.sentinelHost) { - await t.typeText(this.hostInput, parameters.sentinelHost, { replace: true, paste: true }); - } - if (!!parameters.sentinelPort) { - await t.typeText(this.portInput, parameters.sentinelPort, { replace: true, paste: true }); - } - if (!!parameters.sentinelPassword) { - await t.typeText(this.passwordInput, parameters.sentinelPassword, { replace: true, paste: true }); - } - } - - /** - * Adding a new database from RE Cluster via auto-discover flow - * @param parameters the parameters of the database - */ - async addAutodiscoverREClusterDatabase(parameters: AddNewDatabaseParameters): Promise { - - await t - .click(this.addDatabaseButton) - - await t.click(this.redisSotfwareButton); - await t - .typeText(this.hostInput, parameters.host, { replace: true, paste: true }) - .typeText(this.portInput, parameters.port, { replace: true, paste: true }) - .typeText(this.usernameInput, parameters.databaseUsername!, { replace: true, paste: true }) - .typeText(this.passwordInput, parameters.databasePassword!, { replace: true, paste: true }); - } - - /** - * Adding a new database from RE Cloud via auto-discover flow - * @param parameters the parameters of the database - */ - async addAutodiscoverRECloudDatabase(cloudAPIAccessKey: string, cloudAPISecretKey: string): Promise { - - await t - .click(this.addDatabaseButton) - .click(this.addCloudDatabaseButton); - - await t - .typeText(this.accessKeyInput, cloudAPIAccessKey, { replace: true, paste: true }) - .typeText(this.secretKeyInput, cloudAPISecretKey, { replace: true, paste: true }); - } - - /** - * Auto-discover Master Groups from Sentinel - * @param parameters - Parameters of Sentinel: host, port and Sentinel password - */ - async addOssClusterDatabase(parameters: OSSClusterParameters): Promise { - - await t - .click(this.addDatabaseButton) - .click(this.customSettingsButton); - - if (!!parameters.ossClusterHost) { - await t.typeText(this.hostInput, parameters.ossClusterHost, { replace: true, paste: true }); - } - if (!!parameters.ossClusterPort) { - await t.typeText(this.portInput, parameters.ossClusterPort, { replace: true, paste: true }); - } - if (!!parameters.ossClusterDatabaseName) { - await t.typeText(this.databaseAliasInput, parameters.ossClusterDatabaseName, { replace: true, paste: true }); - } - } - - /** - * set copressor value in dropdown - * @param compressor - compressor value - */ - async setCompressorValue(compressor: string): Promise { - if(!await this.selectCompressor.exists) { - await t.click(this.dataCompressorLabel); - } - - await t.click(this.selectCompressor); - await t.click(Selector(`[id="${compressor}"]`)); - } - - /** - * Remove certificate - * @param certificate - certificate - * @param name - name of the certificate - */ - async removeCertificateButton(certificate: TlsCertificates, name: string): Promise { - await t.click(this.securityTab); - const row = Selector('button') - .find('div') - .withText(name); - const removeButton = this.trashIconMsk(certificate); - const removeButtonFooter = Selector('[class^=_popoverFooter]'); - - if (certificate === TlsCertificates.CA) { - await t.click(this.caCertField); - } else { - await t.click(this.clientCertField); - } - - await t.click(row.find(removeButton)); - - await t.click(removeButtonFooter.find(removeButton)); - } -} - -/** - * Add new database parameters - * @param host The hostname of the database - * @param port The port of the database - * @param databaseName The name of the database - * @param databaseUsername The username of the database - * @param databasePassword The password of the database - */ -export type AddNewDatabaseParameters = { - host: string, - port: string, - databaseName?: string, - databaseUsername?: string, - databasePassword?: string, - caCert?: { - name?: string, - certificate?: string - }, - clientCert?: { - name?: string, - certificate?: string, - key?: string - } -}; - -/** - * Sentinel database parameters - * @param sentinelHost The host of sentinel - * @param sentinelPort The port of sentinel - * @param sentinelPassword The password of sentinel - */ -export type SentinelParameters = { - sentinelHost: string, - sentinelPort: string, - masters?: { - alias?: string, - db?: string, - name?: string, - password?: string - }[], - sentinelPassword?: string, - name?: string[] -}; - -/** - * OSS Cluster database parameters - * @param ossClusterHost The host of OSS Cluster - * @param ossClusterPort The port of OSS Cluster - * @param ossClusterDatabaseName Database name for OSS Cluster - */ - -export type OSSClusterParameters = { - ossClusterHost: string, - ossClusterPort: string, - ossClusterDatabaseName: string -}; - -/** - * Already existing database parameters - * @param id The id of the database - * @param host The host of the database - * @param port The port of the database - * @param name The name of the database - * @param connectionType The connection type of the database - * @param lastConnection The last connection time of the database - */ -export type databaseParameters = { - id: string, - host?: string, - port?: string, - name?: string, - connectionType?: string, - lastConnection?: string -}; - -/** - * Nodes in OSS Cluster parameters - * @param host The host of the node - * @param port The port of the node - */ -export type ClusterNodes = { - host: string, - port: string -}; - -/** - * SSH parameters - * @param sshHost The hostname of ssh - * @param sshPort The port of ssh - * @param sshUsername The username of ssh - * @param sshPassword The password of ssh - * @param sshPrivateKey The private key of ssh - * @param sshPassphrase The passphrase of ssh - */ -export type SSHParameters = { - sshHost: string, - sshPort: string, - sshUsername: string, - sshPassword?: string, - sshPrivateKey?: string, - sshPassphrase?: string -}; diff --git a/tests/playwright/pageObjects/dialogs/authorization-dialog.ts b/tests/playwright/pageObjects/dialogs/authorization-dialog.ts deleted file mode 100644 index b472adc4dd..0000000000 --- a/tests/playwright/pageObjects/dialogs/authorization-dialog.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Selector } from 'testcafe'; -import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel'; - -export class AuthorizationDialog { - RedisCloudSigninPanel = new RedisCloudSigninPanel(); - - //COMPONENTS - authDialog = Selector('[data-testid=social-oauth-dialog]'); -} diff --git a/tests/playwright/pageObjects/dialogs/filters-dialog.ts b/tests/playwright/pageObjects/dialogs/filters-dialog.ts deleted file mode 100644 index f640a60ad8..0000000000 --- a/tests/playwright/pageObjects/dialogs/filters-dialog.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Selector, t } from 'testcafe'; - -export class FiltersDialog { - // INPUTS - delimiterCombobox = Selector('[data-testid=delimiter-combobox]'); - delimiterComboboxInput = Selector('[data-test-subj=comboBoxSearchInput]'); - // BUTTONS - treeViewDelimiterValueCancel = Selector('[data-testid=tree-view-cancel-btn]'); - treeViewDelimiterValueSave = Selector('[data-testid=tree-view-apply-btn]'); - sortingBtn = Selector('[data-testid=tree-view-sorting-select]'); - sortingASCoption = Selector('[id=ASC]'); - sortingDESCoption = Selector('[id=DESC]'); - - /** - * Get Delimiter badge selector by title - * @param delimiterTitle title of the delimiter item - */ - getDelimiterBadgeByTitle(delimiterTitle: string): Selector { - return this.delimiterCombobox.find(`span[title='${delimiterTitle}']`); - } - - /** - * Get Delimiter close button selector by title - * @param delimiterTitle title of the delimiter item - */ - getDelimiterCloseBtnByTitle(delimiterTitle: string): Selector { - return this.getDelimiterBadgeByTitle(delimiterTitle).find('button'); - } - - /** - * Add new delimiter - * @param delimiterName name of the delimiter item - */ - async addDelimiterItem(delimiterName: string): Promise { - await t.click(this.delimiterComboboxInput); - await t.typeText(this.delimiterComboboxInput, delimiterName, { paste: true }) - } - - /** - * Delete existing delimiter - * @param delimiterName name of the delimiter item - */ - async removeDelimiterItem(delimiterName: string): Promise { - await t.click(this.getDelimiterCloseBtnByTitle(delimiterName)); - } - - /** - * Remove all existing delimiters in combobox - */ - async clearDelimiterCombobox(): Promise { - const delimiters = this.delimiterCombobox.find('button'); - const count = await delimiters.count; - for (let i = 0; i < count; i++) { - await t.click(delimiters.nth(i)); - } - } -} diff --git a/tests/playwright/pageObjects/dialogs/index.ts b/tests/playwright/pageObjects/dialogs/index.ts deleted file mode 100644 index ebee73341f..0000000000 --- a/tests/playwright/pageObjects/dialogs/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AddRedisDatabaseDialog } from './add-redis-database-dialog'; -import { AuthorizationDialog } from './authorization-dialog'; -import { OnboardingCardsDialog } from './onboarding-cards-dialog'; -import { FiltersDialog } from './filters-dialog'; -import { UserAgreementDialog } from './user-agreement-dialog'; - - -export { - AddRedisDatabaseDialog, - AuthorizationDialog, - OnboardingCardsDialog, - FiltersDialog, - UserAgreementDialog -}; diff --git a/tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts b/tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts deleted file mode 100644 index d7b1d859d7..0000000000 --- a/tests/playwright/pageObjects/dialogs/onboarding-cards-dialog.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Selector, t } from 'testcafe'; - -export class OnboardingCardsDialog { - backButton = Selector('[data-testid=back-btn]'); - nextButton = Selector('[data-testid=next-btn]'); - showMeAroundButton = Selector('span').withText('Show me around'); - skipTourButton = Selector('[data-testid=skip-tour-btn]'); - stepTitle = Selector('[data-testid=step-title]'); - wbOnbardingCommand = Selector('[data-testid=wb-onboarding-command]'); - copyCodeButton = Selector('[data-testid=copy-code-btn]'); - resetOnboardingBtn = Selector('[data-testid=reset-onboarding-btn]'); - - /** - * Verify onboarding step visible based on title - * @param stepName title of the step - */ - async verifyStepVisible(stepName: string): Promise { - await t.expect(this.stepTitle.withText(stepName).exists).ok(`${stepName} step is not visible`); - } - /** - Click next step - */ - async clickNextStep(): Promise { - await t.click(this.nextButton); - } - /** - Click next step until the last step - */ - async clickNextUntilLastStep(): Promise { - do { - await this.clickNextStep(); - } - while (await this.skipTourButton.visible); - } - /** - Start onboarding process - */ - async startOnboarding(): Promise { - await t.click(this.showMeAroundButton); - } - /** - Complete onboarding process - */ - async completeOnboarding(): Promise { - await t.expect(this.showMeAroundButton.exists).notOk('Show me around button still visible'); - await t.expect(this.stepTitle.exists).notOk('Onboarding tooltip still visible'); - } - /** - Click back step - */ - async clickBackStep(): Promise { - await t.click(this.backButton); - } - /** - Click skip tour step - */ - async clickSkipTour(): Promise { - await t.click(this.skipTourButton); - } -} diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts deleted file mode 100644 index ae248d9029..0000000000 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { t, Selector } from 'testcafe'; - -export class UserAgreementDialog { - //------------------------------------------------------------------------------------------- - //DECLARATION OF SELECTORS - //*Declare all elements/components of the relevant page. - //*Target any element/component via data-id, if possible! - //*The following categories are ordered alphabetically (Alerts, Buttons, Checkboxes, etc.). - //------------------------------------------------------------------------------------------- - //COMPONENTS - userAgreementsPopup = Selector('[data-testid=consents-settings-popup]'); - //BUTTONS - submitButton = Selector('[data-testid=btn-submit]'); - switchOptionEula = Selector('[data-testid=switch-option-eula]'); - switchOptionEncryption = Selector('[data-testid=switch-option-encryption]'); - pluginSectionWithText = Selector('[data-testid=plugin-section]'); - recommendedSwitcher = Selector('[data-testid=switch-option-recommended]'); - - //Accept Redis Insight License Terms - async acceptLicenseTerms(): Promise { - if (await this.switchOptionEula.exists) { - await t - .click(this.recommendedSwitcher) - .click(this.switchOptionEula) - .click(this.submitButton) - .expect(this.userAgreementsPopup.exists).notOk('The user agreements popup is not shown', { timeout: 2000 }); - } - } - - /** - * Get state of Recommended switcher - */ - async getRecommendedSwitcherValue(): Promise { - return await this.recommendedSwitcher.getAttribute('aria-checked'); - } -} diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 2299ea2fa3..b27990b9ba 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -1,6 +1,6 @@ -import {defineConfig, devices} from '@playwright/test'; -import * as os from "node:os"; -import {Status} from "allure-js-commons"; +import {defineConfig, devices} from '@playwright/test' +import {Status} from 'allure-js-commons' +import * as os from 'node:os' /** * Read environment variables from file. @@ -24,20 +24,20 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [["line"], ["html"], [ - "allure-playwright", + reporter: [['line'], ['html'], [ + 'allure-playwright', { - resultsDir: "allure-results", + resultsDir: 'allure-results', detail: true, suiteTitle: true, links: { issue: { - nameTemplate: "Issue #%s", - urlTemplate: "https://issues.example.com/%s", + nameTemplate: 'Issue #%s', + urlTemplate: 'https://issues.example.com/%s', }, tms: { - nameTemplate: "TMS #%s", - urlTemplate: "https://tms.example.com/%s", + nameTemplate: 'TMS #%s', + urlTemplate: 'https://tms.example.com/%s', }, jira: { urlTemplate: (v: any) => `https://jira.example.com/browse/${v}`, @@ -45,9 +45,9 @@ export default defineConfig({ }, categories: [ { - name: "foo", - messageRegex: "bar", - traceRegex: "baz", + name: 'foo', + messageRegex: 'bar', + traceRegex: 'baz', matchedStatuses: [Status.FAILED, Status.BROKEN], }, ], @@ -59,6 +59,7 @@ export default defineConfig({ }, }, ]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -68,7 +69,7 @@ export default defineConfig({ trace: 'on-first-retry', testIdAttribute: 'data-testid', video: { - mode: "on", + mode: 'on', size: {width: 1920, height: 1080} }, }, @@ -84,13 +85,21 @@ export default defineConfig({ deviceScaleFactor: undefined, viewport: null, launchOptions:{ - args: ["--start-maximized", - "--disable-component-extensions-with-background-pages", - "--disable-dev-shm-usage", - "--disable-blink-features=AutomationControlled" + args: ['--start-maximized', + '--disable-component-extensions-with-background-pages', + '--disable-dev-shm-usage', + '--disable-blink-features=AutomationControlled' ]} }, }, + { + name: 'localElectron', + use: { + ...devices['Desktop Chrome'], + baseURL: 'https://chrome.desktop/', + + }, + }, // { // name: 'firefox', @@ -129,4 +138,4 @@ export default defineConfig({ // url: 'http://127.0.0.1:3000', // reuseExistingServer: !process.env.CI, // }, -}); +}) diff --git a/tests/playwright/tests/addKeys.spec.ts b/tests/playwright/tests/addKeys.spec.ts index d6b1a6d613..11db17c8d9 100644 --- a/tests/playwright/tests/addKeys.spec.ts +++ b/tests/playwright/tests/addKeys.spec.ts @@ -1,8 +1,9 @@ -// import { rte } from '../helpers/constants'; +import { rte } from '../helpers/constants' +import { Common } from '../helpers/common' // import { DatabaseHelper } from '../../../../helpers/database'; // import { BrowserPage } from '../../../../pageObjects'; // import { commonUrl, ossStandaloneConfig } from '../../../../helpers/conf'; -// import { Common } from '../../../../helpers/common'; + // import { DatabaseAPIRequests } from '../../../../helpers/api/api-database'; // import { APIKeyRequests } from '../../../../helpers/api/api-keys'; // @@ -11,31 +12,31 @@ // const databaseAPIRequests = new DatabaseAPIRequests(); // const apiKeyRequests = new APIKeyRequests(); // -// let keyName = Common.generateWord(10); -// -// fixture `Add keys` -// .meta({ type: 'smoke', rte: rte.standalone }) -// .page(commonUrl) -// .beforeEach(async() => { -// await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig); -// }) -// .afterEach(async() => { -// // Clear and delete database -// await apiKeyRequests.deleteKeyByNameApi(keyName, ossStandaloneConfig.databaseName); -// await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); -// }); -// test('Verify that user can add Hash Key', async t => { -// keyName = Common.generateWord(10); -// // Add Hash key -// await browserPage.addHashKey(keyName); -// // Check the notification message -// const notification = browserPage.Toast.toastHeader.textContent; -// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); -// // Check that new key is displayed in the list -// await browserPage.searchByKeyName(keyName); -// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); -// await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); -// }); +let keyName = Common.generateWord(10); + +fixture `Add keys` + .meta({ type: 'smoke', rte: rte.standalone }) + .page(commonUrl) + .beforeEach(async() => { + await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig); + }) + .afterEach(async() => { + // Clear and delete database + await apiKeyRequests.deleteKeyByNameApi(keyName, ossStandaloneConfig.databaseName); + await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); + }); +test('Verify that user can add Hash Key', async t => { + keyName = Common.generateWord(10); + // Add Hash key + await browserPage.addHashKey(keyName); + // Check the notification message + const notification = browserPage.Toast.toastHeader.textContent; + await t.expect(notification).contains('Key has been added', 'The notification not displayed'); + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName); + const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); + await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); +}); // test('Verify that user can add Set Key', async t => { // keyName = Common.generateWord(10); // // Add Set key diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 54a906a4e8..b3e262ebf6 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,18 +1,18 @@ -import { test, expect } from '@playwright/test'; - -test('has title', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); -}); - -test('get started link', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Click the get started link. - await page.getByRole('link', { name: 'Get started' }).click(); - - // Expects page to have a heading with the name of Installation. - await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); -}); +// import { test, expect } from '@playwright/test'; +// +// test('has title', async ({ page }) => { +// await page.goto('https://playwright.dev/'); +// +// // Expect a title "to contain" a substring. +// await expect(page).toHaveTitle(/Playwright/); +// }); +// +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); +// +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); +// +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index 66986e02b9..dbbf9830b9 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@faker-js/faker@^9.6.0": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d" + integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw== + "@playwright/test@^1.51.0": version "1.51.0" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.0.tgz#8d5c8400b465a0bfdbcf993e390ceecb903ea6d2" @@ -40,11 +45,40 @@ charenc@0.0.2: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypt@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +dotenv-cli@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-8.0.0.tgz#cea1519f5a06c7372a1428fca4605fcf3d50e1cf" + integrity sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw== + dependencies: + cross-spawn "^7.0.6" + dotenv "^16.3.0" + dotenv-expand "^10.0.0" + minimist "^1.2.6" + +dotenv-expand@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@^16.3.0, dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + fsevents@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" @@ -55,6 +89,11 @@ is-buffer@~1.1.6: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -64,6 +103,16 @@ md5@^2.3.0: crypt "0.0.2" is-buffer "~1.1.6" +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + playwright-core@1.51.0: version "1.51.0" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.0.tgz#bb23ea6bb6298242d088ae5e966ffcf8dc9827e8" @@ -78,7 +127,26 @@ playwright@1.51.0: optionalDependencies: fsevents "2.3.2" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + undici-types@~6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" From 7dbb436e4f4fccf3461a8d5bdddba597ff310940 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 14 Mar 2025 16:48:37 +0200 Subject: [PATCH 006/128] local env create --- .../playwright/local-docker-environment/.env | 12 + .../create_local_environment.sh | 7 + .../destroy_local_environment.sh | 5 + .../local-docker-environment/e2e.Dockerfile | 18 ++ .../local.web.docker-compose.yml | 59 ++++ .../rte.docker-compose.yml | 299 ++++++++++++++++++ 6 files changed, 400 insertions(+) create mode 100644 tests/playwright/local-docker-environment/.env create mode 100755 tests/playwright/local-docker-environment/create_local_environment.sh create mode 100755 tests/playwright/local-docker-environment/destroy_local_environment.sh create mode 100644 tests/playwright/local-docker-environment/e2e.Dockerfile create mode 100644 tests/playwright/local-docker-environment/local.web.docker-compose.yml create mode 100644 tests/playwright/local-docker-environment/rte.docker-compose.yml diff --git a/tests/playwright/local-docker-environment/.env b/tests/playwright/local-docker-environment/.env new file mode 100644 index 0000000000..822fcf3dac --- /dev/null +++ b/tests/playwright/local-docker-environment/.env @@ -0,0 +1,12 @@ +COMMON_URL=https://app:5540 +API_URL=https://app:5540/api +BUILD_TYPE=DOCKER_ON_PREMISE +OSS_SENTINEL_PASSWORD=password +RI_NOTIFICATION_UPDATE_URL=https://s3.amazonaws.com/redisinsight.test/public/tests/e2e/notifications.json +RI_NOTIFICATION_SYNC_INTERVAL=30000 +RI_FEATURES_CONFIG_URL=http://static-server:5551/remote/features-config.json +RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 +TEST_BIG_DB_DUMP= +RI_ENCRYPTION_KEY= +RI_SERVER_TLS_CERT= +RI_SERVER_TLS_KEY= diff --git a/tests/playwright/local-docker-environment/create_local_environment.sh b/tests/playwright/local-docker-environment/create_local_environment.sh new file mode 100755 index 0000000000..d9736278e2 --- /dev/null +++ b/tests/playwright/local-docker-environment/create_local_environment.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +mkdir "rihomedir" +sudo chmod -R 777 rihomedir/ + +docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml up -d + diff --git a/tests/playwright/local-docker-environment/destroy_local_environment.sh b/tests/playwright/local-docker-environment/destroy_local_environment.sh new file mode 100755 index 0000000000..5e4dbd0b02 --- /dev/null +++ b/tests/playwright/local-docker-environment/destroy_local_environment.sh @@ -0,0 +1,5 @@ +#!/bin/bash +docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml down + +rm -rf plugins remote report results rte test-data rihomedir + diff --git a/tests/playwright/local-docker-environment/e2e.Dockerfile b/tests/playwright/local-docker-environment/e2e.Dockerfile new file mode 100644 index 0000000000..306c5a77f9 --- /dev/null +++ b/tests/playwright/local-docker-environment/e2e.Dockerfile @@ -0,0 +1,18 @@ +FROM testcafe/testcafe + +USER root + +WORKDIR /usr/src/app + +RUN apk add --no-cache bash curl + +COPY package.json yarn.lock ./ + +RUN npx yarn + +COPY . . + +RUN chmod +x wait-for-it.sh +RUN chmod +x upload-custom-plugins.sh + +ENTRYPOINT ["npx", "yarn", "test:chrome:ci"] diff --git a/tests/playwright/local-docker-environment/local.web.docker-compose.yml b/tests/playwright/local-docker-environment/local.web.docker-compose.yml new file mode 100644 index 0000000000..788a88304e --- /dev/null +++ b/tests/playwright/local-docker-environment/local.web.docker-compose.yml @@ -0,0 +1,59 @@ +version: "3.4" + +services: + e2e: + build: + context: . + dockerfile: e2e.Dockerfile + tty: true + volumes: + - ./results:/usr/src/app/results + - ./report:/usr/src/app/report + - ./plugins:/usr/src/app/plugins + - rihomedir:/root/.redis-insight + - tmp:/tmp + - ./remote:/root/remote + # - ./rdi:/root/rdi + env_file: + - ./.env + entrypoint: [ + './upload-custom-plugins.sh', + ] + environment: + TEST_FILES: $TEST_FILES + E2E_CLOUD_DATABASE_HOST: $E2E_CLOUD_DATABASE_HOST + E2E_CLOUD_DATABASE_PORT: $E2E_CLOUD_DATABASE_PORT + E2E_CLOUD_DATABASE_PASSWORD: $E2E_CLOUD_DATABASE_PASSWORD + E2E_CLOUD_DATABASE_USERNAME: $E2E_CLOUD_DATABASE_USERNAME + E2E_CLOUD_DATABASE_NAME: $E2E_CLOUD_DATABASE_NAME + E2E_CLOUD_API_ACCESS_KEY: $E2E_CLOUD_API_ACCESS_KEY + E2E_CLOUD_API_SECRET_KEY: $E2E_CLOUD_API_SECRET_KEY + REMOTE_FOLDER_PATH: "/root/remote" + command: [ + './wait-for-it.sh', 'redis-enterprise:12000', '-s', '-t', '120', + '--', + 'npm', 'run', 'test:chrome:ci' + ] + + # Built image + app: + logging: + driver: none + image: redisinsight:amd64 + env_file: + - ./.env + environment: + RI_ENCRYPTION_KEY: $RI_ENCRYPTION_KEY + RI_SERVER_TLS_CERT: $RI_SERVER_TLS_CERT + RI_SERVER_TLS_KEY: $RI_SERVER_TLS_KEY + BUILD_TYPE: DOCKER_ON_PREMISE + volumes: + - ./rihomedir:/data + - tmp:/tmp + - ./test-data:/test-data + ports: + - 5540:5540 + +volumes: + tmp: + rihomedir: diff --git a/tests/playwright/local-docker-environment/rte.docker-compose.yml b/tests/playwright/local-docker-environment/rte.docker-compose.yml new file mode 100644 index 0000000000..ba425cd5a1 --- /dev/null +++ b/tests/playwright/local-docker-environment/rte.docker-compose.yml @@ -0,0 +1,299 @@ +version: "3.4" + +services: + static-server: + logging: &logging + driver: none + build: + context: . + dockerfile: static-server.Dockerfile + volumes: + - ./remote:/app/remote + ports: + - 5551:5551 + # RDI mocked + # rdi: + # logging: *logging + # build: + # context: rte/rdi + # dockerfile: Dockerfile + # volumes: + # - ./rdi:/data + # ports: + # - 4000:4000 + # ssh + ssh: + logging: *logging + image: lscr.io/linuxserver/openssh-server:9.7_p1-r4-ls172 + environment: + - PASSWORD_ACCESS=true + - USER_PASSWORD=pass + - USER_NAME=u + - DOCKER_MODS=linuxserver/mods:openssh-server-ssh-tunnel + - PUBLIC_KEY_DIR=/keys/pub + volumes: + - ./rte/ssh/keys:/keys + ports: + - 2222:2222 + networks: + default: + ipv4_address: 172.31.100.245 + ssh: + ipv4_address: 172.33.100.245 + + # oss standalone + oss-standalone: + logging: *logging + image: redislabs/redismod + command: [ + "--loadmodule", "/usr/lib/redis/modules/redisearch.so", + "--loadmodule", "/usr/lib/redis/modules/redisgraph.so", + "--loadmodule", "/usr/lib/redis/modules/redistimeseries.so", + "--loadmodule", "/usr/lib/redis/modules/rejson.so", + "--loadmodule", "/usr/lib/redis/modules/redisbloom.so" + ] + ports: + - 8100:6379 + + oss-standalone-empty: + logging: *logging + image: redislabs/redismod + command: [ + "--loadmodule", "/usr/lib/redis/modules/redisearch.so", + "--loadmodule", "/usr/lib/redis/modules/redisgraph.so", + "--loadmodule", "/usr/lib/redis/modules/redistimeseries.so", + "--loadmodule", "/usr/lib/redis/modules/rejson.so", + "--loadmodule", "/usr/lib/redis/modules/redisbloom.so" + ] + ports: + - 8105:6379 + + # oss standalone v5 + oss-standalone-v5: + logging: *logging + image: redis:5 + ports: + - 8101:6379 + networks: + default: + ipv4_address: 172.31.100.111 + ssh: + ipv4_address: 172.33.100.111 + + # oss standalone v7 + oss-standalone-v7: + logging: *logging + image: redis:7.4-rc2 + ports: + - 8108:6379 + networks: + default: + ipv4_address: 172.31.100.112 + ssh: + ipv4_address: 172.33.100.112 + + # oss standalone v8 + oss-standalone-v8: + logging: *logging + image: redis:8.0-M02 + ports: + - 8109:6379 + networks: + default: + ipv4_address: 172.31.100.113 + ssh: + ipv4_address: 172.33.100.113 + + # oss standalone redisearch + oss-standalone-redisearch: + logging: *logging + image: redislabs/redismod + ports: + - 8102:6379 + + oss-standalone-redisgears-2-0: + logging: *logging + image: redislabs/redisgears:edge + ports: + - 8106:6379 + + oss-standalone-big: + logging: *logging + build: + context: ./rte/oss-standalone-big + dockerfile: Dockerfile + args: + TEST_DB_DUMP: $TEST_BIG_DB_DUMP + ports: + - 8103:6379 + + # oss standalone tls + oss-standalone-tls: + logging: *logging + build: + context: ./rte/oss-standalone-tls + dockerfile: Dockerfile + ports: + - 8104:6379 + + # oss sentinel + oss-sentinel: + logging: *logging + build: ./rte/oss-sentinel + depends_on: + - oss-sentinel-primary-1 + - oss-sentinel-primary-2 + ports: + - 28100:26379 + + oss-sentinel-primary-1: + logging: *logging + image: redis:5 + + oss-sentinel-primary-2: + logging: *logging + image: redis:5 + + # oss cluster (v7) + cluster-plain-creator-7: + logging: *logging + build: + context: ./rte/oss-cluster-7 + dockerfile: creator.Dockerfile + depends_on: + - master-plain-7-1 + - master-plain-7-2 + - master-plain-7-3 + master-plain-7-1: + logging: *logging + build: &cluster-plain-7-build ./rte/oss-cluster-7 + ports: + - 8200:6379 + networks: + default: + ipv4_address: 172.31.100.211 + ssh: + ipv4_address: 172.33.100.211 + master-plain-7-2: + logging: *logging + build: *cluster-plain-7-build + networks: + default: + ipv4_address: 172.31.100.212 + master-plain-7-3: + logging: *logging + build: *cluster-plain-7-build + networks: + default: + ipv4_address: 172.31.100.213 + + # oss cluster (v7) with rediserch > 2.2 + cluster-rs-creator-7: + logging: *logging + build: + context: &cluster-rs-7-build ./rte/oss-cluster-7-rs + dockerfile: creator.Dockerfile + depends_on: + - master-rs-7-1 + - master-rs-7-2 + - master-rs-7-3 + master-rs-7-1: + logging: *logging + build: *cluster-rs-7-build + ports: + - 8221:6379 + networks: + default: + ipv4_address: 172.31.100.221 + master-rs-7-2: + logging: *logging + build: *cluster-rs-7-build + networks: + default: + ipv4_address: 172.31.100.222 + master-rs-7-3: + logging: *logging + build: *cluster-rs-7-build + networks: + default: + ipv4_address: 172.31.100.223 + + # oss cluster with redisgears 2 + gears-cluster-2-0-creator: + logging: *logging + image: redis:latest + entrypoint: ['/bin/sh', '-c', 'redis-cli --cluster create 172.31.100.191:6379 172.31.100.192:6379 172.31.100.193:6379 172.31.100.194:6379 172.31.100.195:6379 172.31.100.196:6379 --cluster-replicas 1 --cluster-yes && tail -f /dev/null'] + depends_on: + - gears-cluster-2-0-node-1 + - gears-cluster-2-0-node-2 + - gears-cluster-2-0-node-3 + - gears-cluster-2-0-node-4 + - gears-cluster-2-0-node-5 + - gears-cluster-2-0-node-6 + gears-cluster-2-0-node-1: + logging: *logging + image: &gears-cluster-img redislabs/redisgears:edge + command: &gears-cluster-cmd redis-server --protected-mode no --loadmodule /build/target/release/libredisgears.so v8-plugin-path /build/target/release/libredisgears_v8_plugin.so --cluster-enabled yes + networks: + default: + ipv4_address: 172.31.100.191 + gears-cluster-2-0-node-2: + logging: *logging + image: *gears-cluster-img + command: *gears-cluster-cmd + networks: + default: + ipv4_address: 172.31.100.192 + gears-cluster-2-0-node-3: + logging: *logging + image: *gears-cluster-img + command: *gears-cluster-cmd + networks: + default: + ipv4_address: 172.31.100.193 + gears-cluster-2-0-node-4: + logging: *logging + image: *gears-cluster-img + command: *gears-cluster-cmd + networks: + default: + ipv4_address: 172.31.100.194 + gears-cluster-2-0-node-5: + logging: *logging + image: *gears-cluster-img + command: *gears-cluster-cmd + networks: + default: + ipv4_address: 172.31.100.195 + gears-cluster-2-0-node-6: + logging: *logging + image: *gears-cluster-img + command: *gears-cluster-cmd + networks: + default: + ipv4_address: 172.31.100.196 + + # redis enterprise + redis-enterprise: + logging: *logging + build: ./rte/redis-enterprise + cap_add: + - sys_resource + ports: + - 19443:9443 + - 12000:12000 +networks: + default: + name: "e2e-private-network" + ipam: + driver: default + config: + - subnet: 172.31.100.0/24 + gateway: 172.31.100.1 + ssh: + name: "e2e-ssh-network" + ipam: + driver: default + config: + - subnet: 172.33.100.0/24 + gateway: 172.33.100.1 From d8b55fb41adaab5473fb5f66aa229ccfec2c87b3 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 14 Mar 2025 21:25:27 +0200 Subject: [PATCH 007/128] browser starting to local url --- tests/playwright/fixtures/open-ri.ts | 39 ++++ tests/playwright/helpers/conf.ts | 180 ++++++++++++++++++ .../create_local_environment.sh | 4 +- .../destroy_local_environment.sh | 4 +- tests/playwright/pageObjects/base-page.ts | 8 + .../pageObjects/user-agreement-dialog.ts | 36 ++++ tests/playwright/playwright.config.ts | 24 ++- tests/playwright/tests/addKeys.spec.ts | 121 +++--------- tests/playwright/tests/example.spec.ts | 30 ++- 9 files changed, 329 insertions(+), 117 deletions(-) create mode 100644 tests/playwright/fixtures/open-ri.ts create mode 100644 tests/playwright/helpers/conf.ts create mode 100644 tests/playwright/pageObjects/user-agreement-dialog.ts diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts new file mode 100644 index 0000000000..1018a9d35a --- /dev/null +++ b/tests/playwright/fixtures/open-ri.ts @@ -0,0 +1,39 @@ +import { test as base } from '@playwright/test' +import BasePage from '../pageObjects/base-page' +import {UserAgreementDialog} from "../pageObjects/user-agreement-dialog"; + +type OpenRedisInsight = { + basePage: BasePage; + dialogUserAgreement: UserAgreementDialog; +} + +export const test = base.extend({ + + // context: async ({ browser }, use) => { + // const context = await browser.newContext() + // await context.clearCookies() + // await context.clearPermissions() + // // await context.storageState({ path: 'emptyState.json' }) + // await use(context) + // await context.close() + // }, + // basePage: async ({ context }, use) => { + basePage: async ({ page }, use) => { + + // const page = await context.newPage() + // Set up the fixture. + const basePage = new BasePage(page) + await basePage.navigateToHomeUrl() + + await use(basePage) + + }, + dialogUserAgreement: async ({ page }, use) => { + const userAgreementDialog = new UserAgreementDialog(page) + await userAgreementDialog.acceptLicenseTerms() + await use(new UserAgreementDialog(page)) + }, + +}) + +export { expect } from '@playwright/test' diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts new file mode 100644 index 0000000000..a07f8fd4de --- /dev/null +++ b/tests/playwright/helpers/conf.ts @@ -0,0 +1,180 @@ +import {faker} from '@faker-js/faker' +import * as os from 'os' +import * as fs from 'fs' +import { join as joinPath } from 'path' +import * as path from 'path' + +// Urls for using in the tests +export const commonUrl = process.env.COMMON_URL || 'https://localhost:5540' +export const apiUrl = process.env.API_URL || 'https://localhost:5540/api' +export const googleUser = process.env.GOOGLE_USER || '' +export const googleUserPassword = process.env.GOOGLE_USER_PASSWORD || '' +export const samlUser = process.env.E2E_SSO_EMAIL || '' +export const samlUserPassword = process.env.E2E_SSO_PASSWORD || '' + +export const workingDirectory = process.env.RI_APP_FOLDER_ABSOLUTE_PATH + || (joinPath(os.homedir(), process.env.RI_APP_FOLDER_NAME || '.redis-insight')) +export const fileDownloadPath = joinPath(os.homedir(), 'Downloads') +const uniqueId = faker.string.alphanumeric({ length: 10 }) + +export const ossStandaloneConfig = { + host: process.env.OSS_STANDALONE_HOST || 'oss-standalone-v8', + port: process.env.OSS_STANDALONE_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_DATABASE_NAME || 'test_standalone'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_USERNAME, + databasePassword: process.env.OSS_STANDALONE_PASSWORD +} + +export const ossStandaloneConfigEmpty = { + host: process.env.OSS_STANDALONE_EMPTY_HOST || 'oss-standalone-empty', + port: process.env.OSS_STANDALONE_EMPTY_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_EMPTY_DATABASE_NAME || 'test_standalone_empty'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_EMPTY_USERNAME, + databasePassword: process.env.OSS_STANDALONE_EMPTY_PASSWORD +} + +export const ossStandaloneV5Config = { + host: process.env.OSS_STANDALONE_V5_HOST || 'oss-standalone-v5', + port: process.env.OSS_STANDALONE_V5_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_V5_DATABASE_NAME || 'test_standalone-v5'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_V5_USERNAME, + databasePassword: process.env.OSS_STANDALONE_V5_PASSWORD +} + +export const ossStandaloneV7Config = { + host: process.env.OSS_STANDALONE_V7_HOST || 'oss-standalone-v7', + port: process.env.OSS_STANDALONE_V7_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_V7_DATABASE_NAME || 'test_standalone-v7'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_V7_USERNAME, + databasePassword: process.env.OSS_STANDALONE_V7_PASSWORD +} + +export const ossStandaloneV6Config = { + host: process.env.OSS_STANDALONE_V8_HOST || 'oss-standalone', + port: process.env.OSS_STANDALONE_V8_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_V8_DATABASE_NAME || 'test_standalone-v6'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_V8_USERNAME, + databasePassword: process.env.OSS_STANDALONE_V8_PASSWORD +} + +export const ossStandaloneRedisearch = { + host: process.env.OSS_STANDALONE_REDISEARCH_HOST || 'oss-standalone-redisearch', + port: process.env.OSS_STANDALONE_REDISEARCH_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_REDISEARCH_DATABASE_NAME || 'test_standalone-redisearch'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_REDISEARCH_USERNAME, + databasePassword: process.env.OSS_STANDALONE_REDISEARCH_PASSWORD +} + +export const ossClusterConfig = { + ossClusterHost: process.env.OSS_CLUSTER_HOST || 'master-plain-7-1', + ossClusterPort: process.env.OSS_CLUSTER_PORT || '6379', + ossClusterDatabaseName: `${process.env.OSS_CLUSTER_DATABASE_NAME || 'test_cluster'}-${uniqueId}` +} + +export const ossSentinelConfig = { + sentinelHost: process.env.OSS_SENTINEL_HOST || 'oss-sentinel', + sentinelPort: process.env.OSS_SENTINEL_PORT || '26379', + sentinelPassword: process.env.OSS_SENTINEL_PASSWORD || 'password', + masters: [{ + alias: `primary-group-1}-${uniqueId}`, + db: '0', + name: 'primary-group-1', + password: 'defaultpass' + }, + { + alias: `primary-group-2}-${uniqueId}`, + db: '0', + name: 'primary-group-2', + password: 'defaultpass' + }], + name: ['primary-group-1', 'primary-group-2'] +} + +export const redisEnterpriseClusterConfig = { + host: process.env.RE_CLUSTER_HOST || 'redis-enterprise', + port: process.env.RE_CLUSTER_PORT || '9443', + databaseName: process.env.RE_CLUSTER_DATABASE_NAME || 'test-re-standalone', + databaseUsername: process.env.RE_CLUSTER_ADMIN_USER || 'demo@redislabs.com', + databasePassword: process.env.RE_CLUSTER_ADMIN_PASSWORD || '123456' +} + +export const invalidOssStandaloneConfig = { + host: 'oss-standalone-invalid', + port: '1010', + databaseName: `${process.env.OSS_STANDALONE_INVALID_DATABASE_NAME || 'test_standalone-invalid'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_INVALID_USERNAME, + databasePassword: process.env.OSS_STANDALONE_INVALID_PASSWORD +} + +export const ossStandaloneBigConfig = { + host: process.env.OSS_STANDALONE_BIG_HOST || 'oss-standalone-big', + port: process.env.OSS_STANDALONE_BIG_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_BIG_DATABASE_NAME || 'test_standalone_big'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_BIG_USERNAME, + databasePassword: process.env.OSS_STANDALONE_BIG_PASSWORD +} + +export const cloudDatabaseConfig = { + host: process.env.E2E_CLOUD_DATABASE_HOST || '', + port: process.env.E2E_CLOUD_DATABASE_PORT || '', + databaseName: `${process.env.E2E_CLOUD_DATABASE_NAME || 'cloud-database'}-${uniqueId}`, + databaseUsername: process.env.E2E_CLOUD_DATABASE_USERNAME, + databasePassword: process.env.E2E_CLOUD_DATABASE_PASSWORD, + accessKey: process.env.E2E_CLOUD_API_ACCESS_KEY || '', + secretKey: process.env.E2E_CLOUD_API_SECRET_KEY || '' +} + +export const ossStandaloneNoPermissionsConfig = { + host: process.env.OSS_STANDALONE_NOPERM_HOST || 'oss-standalone', + port: process.env.OSS_STANDALONE_NOPERM_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_NOPERM_DATABASE_NAME || 'oss-standalone-no-permissions'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_NOPERM_USERNAME || 'noperm', + databasePassword: process.env.OSS_STANDALONE_NOPERM_PASSWORD +} + +export const ossStandaloneForSSHConfig = { + host: process.env.OSS_STANDALONE_SSH_HOST || '172.33.100.111', + port: process.env.OSS_STANDALONE_SSH_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_SSH_DATABASE_NAME || 'oss-standalone-for-ssh'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_SSH_USERNAME, + databasePassword: process.env.OSS_STANDALONE_SSH_PASSWORD +} + +export const ossClusterForSSHConfig = { + host: process.env.OSS_CLUSTER_SSH_HOST || '172.31.100.211', + port: process.env.OSS_CLUSTER_SSH_PORT || '6379', + databaseName: `${process.env.OSS_CLUSTER_SSH_DATABASE_NAME || 'oss-cluster-for-ssh'}-${uniqueId}`, + databaseUsername: process.env.OSS_CLUSTER_SSH_USERNAME, + databasePassword: process.env.OSS_CLUSTER_SSH_PASSWORD +} + +export const ossStandaloneTlsConfig = { + host: process.env.OSS_STANDALONE_TLS_HOST || 'oss-standalone-tls', + port: process.env.OSS_STANDALONE_TLS_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_TLS_DATABASE_NAME || 'test_standalone_tls'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_TLS_USERNAME, + databasePassword: process.env.OSS_STANDALONE_TLS_PASSWORD, + caCert: { + name: `ca}-${uniqueId}`, + certificate: process.env.E2E_CA_CRT || fs.readFileSync(path.resolve(__dirname, '../rte/oss-standalone-tls/certs/redisCA.crt'), 'utf-8') + }, + clientCert: { + name: `client}-${uniqueId}`, + certificate: process.env.E2E_CLIENT_CRT || fs.readFileSync(path.resolve(__dirname, '../rte/oss-standalone-tls/certs/redis.crt'), 'utf-8'), + key: process.env.E2E_CLIENT_KEY || fs.readFileSync(path.resolve(__dirname, '../rte/oss-standalone-tls/certs/redis.key'), 'utf-8') + } +} + +export const ossStandaloneRedisGears = { + host: process.env.OSS_STANDALONE_REDISGEARS_HOST || 'oss-standalone-redisgears-2-0', + port: process.env.OSS_STANDALONE_REDISGEARS_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_REDISGEARS_DATABASE_NAME || 'test_standalone_redisgears'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_REDISGEARS_USERNAME, + databasePassword: process.env.OSS_STANDALONE_REDISGEARS_PASSWORD +} + +export const ossClusterRedisGears = { + ossClusterHost: process.env.OSS_CLUSTER_REDISGEARS_2_HOST || 'gears-cluster-2-0-node-1', + ossClusterPort: process.env.OSS_CLUSTER_REDISGEARS_2_PORT || '6379', + ossClusterDatabaseName: `${process.env.OSS_CLUSTER_REDISGEARS_2_NAME || 'test_cluster-gears-2.0'}-${uniqueId}` +} diff --git a/tests/playwright/local-docker-environment/create_local_environment.sh b/tests/playwright/local-docker-environment/create_local_environment.sh index d9736278e2..bec69e9ef9 100755 --- a/tests/playwright/local-docker-environment/create_local_environment.sh +++ b/tests/playwright/local-docker-environment/create_local_environment.sh @@ -1,7 +1,7 @@ #!/bin/bash -mkdir "rihomedir" -sudo chmod -R 777 rihomedir/ +mkdir -p "rihomedir" +chmod +w rihomedir/ docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml up -d diff --git a/tests/playwright/local-docker-environment/destroy_local_environment.sh b/tests/playwright/local-docker-environment/destroy_local_environment.sh index 5e4dbd0b02..7224d6c83e 100755 --- a/tests/playwright/local-docker-environment/destroy_local_environment.sh +++ b/tests/playwright/local-docker-environment/destroy_local_environment.sh @@ -1,5 +1,5 @@ #!/bin/bash -docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml down - +docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml down && +sleep 1 rm -rf plugins remote report results rte test-data rihomedir diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 53ece44815..8ee48e1cc6 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -11,6 +11,10 @@ export default class BasePage { await this.page.goto(url) } + async navigateToHomeUrl(): Promise { + await this.page.goto('/') + } + async click(selector: string): Promise { await this.page.click(selector) } @@ -26,4 +30,8 @@ export default class BasePage { async isVisible(selector: string): Promise { return this.page.isVisible(selector) } + + protected getByTestId(testId: string, options?: { timeout?: number }): Locator { + return this.page.getByTestId(testId, options) + } } diff --git a/tests/playwright/pageObjects/user-agreement-dialog.ts b/tests/playwright/pageObjects/user-agreement-dialog.ts new file mode 100644 index 0000000000..746455a40f --- /dev/null +++ b/tests/playwright/pageObjects/user-agreement-dialog.ts @@ -0,0 +1,36 @@ +import { expect, Locator, Page } from '@playwright/test' +import BasePage from './base-page' + +export class UserAgreementDialog extends BasePage { + + // Private selectors + private readonly userAgreementsPopup: Locator + private readonly submitButton: Locator + private readonly switchOptionEula: Locator + private readonly switchOptionEncryption: Locator + private readonly pluginSectionWithText: Locator + private readonly recommendedSwitcher: Locator + + constructor(page: Page) { + super(page) + this.userAgreementsPopup = this.getByTestId('consents-settings-popup') + this.submitButton = this.getByTestId('btn-submit') + this.switchOptionEula = this.getByTestId('switch-option-eula') + this.switchOptionEncryption = this.getByTestId('switch-option-encryption') + this.pluginSectionWithText = this.getByTestId('plugin-section') + this.recommendedSwitcher = this.getByTestId('switch-option-recommended') + } + + async acceptLicenseTerms(): Promise { + if (await this.switchOptionEula.isVisible()) { + await this.recommendedSwitcher.click() + await this.switchOptionEula.click() + await this.submitButton.click() + await expect(this.userAgreementsPopup).not.toBeVisible({ timeout: 2000 }) + } + } + + async getRecommendedSwitcherValue(): Promise { + return await this.recommendedSwitcher.getAttribute('aria-checked') + } +} diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index b27990b9ba..0e34044cad 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -1,7 +1,14 @@ -import {defineConfig, devices} from '@playwright/test' +import {defineConfig, devices } from '@playwright/test' import {Status} from 'allure-js-commons' import * as os from 'node:os' + +declare module '@playwright/test' { + interface PlaywrightTestOptions { + apiUrl?: string; // add your custom variables here + } +} + /** * Read environment variables from file. * https://github.com/motdotla/dotenv @@ -15,6 +22,15 @@ import * as os from 'node:os' */ export default defineConfig({ testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 300 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -80,7 +96,8 @@ export default defineConfig({ name: 'localChromium', use: { ...devices['Desktop Chrome'], - baseURL: 'https://chrome.desktop/', + baseURL: process.env.COMMON_URL || 'https://localhost:5540', + apiUrl: process.env.API_URL || 'https://localhost:5540/api', headless: false, deviceScaleFactor: undefined, viewport: null, @@ -88,7 +105,8 @@ export default defineConfig({ args: ['--start-maximized', '--disable-component-extensions-with-background-pages', '--disable-dev-shm-usage', - '--disable-blink-features=AutomationControlled' + '--disable-blink-features=AutomationControlled', + '--ignore-certificate-errors' ]} }, }, diff --git a/tests/playwright/tests/addKeys.spec.ts b/tests/playwright/tests/addKeys.spec.ts index 11db17c8d9..ac5dbeee6b 100644 --- a/tests/playwright/tests/addKeys.spec.ts +++ b/tests/playwright/tests/addKeys.spec.ts @@ -1,102 +1,39 @@ -import { rte } from '../helpers/constants' -import { Common } from '../helpers/common' -// import { DatabaseHelper } from '../../../../helpers/database'; -// import { BrowserPage } from '../../../../pageObjects'; -// import { commonUrl, ossStandaloneConfig } from '../../../../helpers/conf'; - -// import { DatabaseAPIRequests } from '../../../../helpers/api/api-database'; -// import { APIKeyRequests } from '../../../../helpers/api/api-keys'; +// import { rte } from '../helpers/constants' +// import { Common } from '../helpers/common' +// // import { DatabaseHelper } from '../../../../helpers/database'; +// // import { BrowserPage } from '../../../../pageObjects'; +// // import { commonUrl, ossStandaloneConfig } from '../../../../helpers/conf'; // -// const browserPage = new BrowserPage(); -// const databaseHelper = new DatabaseHelper(); -// const databaseAPIRequests = new DatabaseAPIRequests(); -// const apiKeyRequests = new APIKeyRequests(); +// // import { DatabaseAPIRequests } from '../../../../helpers/api/api-database'; +// // import { APIKeyRequests } from '../../../../helpers/api/api-keys'; +// // +// // const browserPage = new BrowserPage(); +// // const databaseHelper = new DatabaseHelper(); +// // const databaseAPIRequests = new DatabaseAPIRequests(); +// // const apiKeyRequests = new APIKeyRequests(); +// // +// let keyName = Common.generateWord(10) //done // -let keyName = Common.generateWord(10); - -fixture `Add keys` - .meta({ type: 'smoke', rte: rte.standalone }) - .page(commonUrl) - .beforeEach(async() => { - await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig); - }) - .afterEach(async() => { - // Clear and delete database - await apiKeyRequests.deleteKeyByNameApi(keyName, ossStandaloneConfig.databaseName); - await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); - }); -test('Verify that user can add Hash Key', async t => { - keyName = Common.generateWord(10); - // Add Hash key - await browserPage.addHashKey(keyName); - // Check the notification message - const notification = browserPage.Toast.toastHeader.textContent; - await t.expect(notification).contains('Key has been added', 'The notification not displayed'); - // Check that new key is displayed in the list - await browserPage.searchByKeyName(keyName); - const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); - await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); -}); -// test('Verify that user can add Set Key', async t => { +// fixture `Add keys` //NA +// .meta({ type: 'smoke', rte: rte.standalone }) +// .page(commonUrl) +// .beforeEach(async() => { +// await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig); +// }) +// .afterEach(async() => { +// // Clear and delete database +// await apiKeyRequests.deleteKeyByNameApi(keyName, ossStandaloneConfig.databaseName); +// await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); +// }); +// test('Verify that user can add Hash Key', async t => { // keyName = Common.generateWord(10); -// // Add Set key -// await browserPage.addSetKey(keyName); +// // Add Hash key +// await browserPage.addHashKey(keyName); // // Check the notification message // const notification = browserPage.Toast.toastHeader.textContent; // await t.expect(notification).contains('Key has been added', 'The notification not displayed'); // // Check that new key is displayed in the list // await browserPage.searchByKeyName(keyName); // const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); -// await t.expect(isKeyIsDisplayedInTheList).ok('The Set key is not added'); -// }); -// test('Verify that user can add List Key', async t => { -// keyName = Common.generateWord(10); -// // Add List key -// await browserPage.addListKey(keyName); -// // Check the notification message -// const notification = browserPage.Toast.toastHeader.textContent; -// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); -// // Check that new key is displayed in the list -// await browserPage.searchByKeyName(keyName); -// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); -// await t.expect(isKeyIsDisplayedInTheList).ok('The List key is not added'); -// }); -// test('Verify that user can add String Key', async t => { -// keyName = Common.generateWord(10); -// // Add String key -// await browserPage.addStringKey(keyName); -// // Check the notification message -// const notification = browserPage.Toast.toastHeader.textContent; -// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); -// // Check that new key is displayed in the list -// await browserPage.searchByKeyName(keyName); -// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); -// await t.expect(isKeyIsDisplayedInTheList).ok('The String key is not added'); -// }); -// test('Verify that user can add ZSet Key', async t => { -// keyName = Common.generateWord(10); -// // Add ZSet key -// await browserPage.addZSetKey(keyName, '111'); -// // Check the notification message -// const notification = browserPage.Toast.toastHeader.textContent; -// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); -// // Check that new key is displayed in the list -// await browserPage.searchByKeyName(keyName); -// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); -// await t.expect(isKeyIsDisplayedInTheList).ok('The ZSet key is not added'); -// }); -// test('Verify that user can add JSON Key', async t => { -// keyName = Common.generateWord(10); -// const keyTTL = '2147476121'; -// const value = '{"name":"xyz"}'; -// -// // Add JSON key -// await browserPage.addJsonKey(keyName, value, keyTTL); -// // Check the notification message -// const notification = browserPage.Toast.toastHeader.textContent; -// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); -// // Check that new key is displayed in the list -// await browserPage.searchByKeyName(keyName); -// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); -// await t.expect(isKeyIsDisplayedInTheList).ok('The JSON key is not added'); +// await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); // }); diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index b3e262ebf6..9e981da25f 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,18 +1,12 @@ -// import { test, expect } from '@playwright/test'; -// -// test('has title', async ({ page }) => { -// await page.goto('https://playwright.dev/'); -// -// // Expect a title "to contain" a substring. -// await expect(page).toHaveTitle(/Playwright/); -// }); -// -// test('get started link', async ({ page }) => { -// await page.goto('https://playwright.dev/'); -// -// // Click the get started link. -// await page.getByRole('link', { name: 'Get started' }).click(); -// -// // Expects page to have a heading with the name of Installation. -// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); -// }); +import { test, expect } from '../fixtures/open-ri' + +test.beforeEach(async ({ basePage , dialogUserAgreement}) => { + await dialogUserAgreement.acceptLicenseTerms() + await basePage.getText('sa') + +}) + +test('basic test', async ({ basePage, page }) => { + await basePage.click('button') + await expect(page.getByTestId('todo-title')).toContainText(['something nice']) +}) From 298a4fbfd56007c2160a0d466f153e595d4cb300 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 14 Mar 2025 23:18:02 +0200 Subject: [PATCH 008/128] more POs --- tests/playwright/helpers/common.ts | 10 +- tests/playwright/helpers/database.ts | 192 +++++----- .../pageObjects/base-overview-page.ts | 183 ++++++++++ tests/playwright/pageObjects/base-page.ts | 18 +- .../pageObjects/components/common/toast.ts | 33 ++ .../dialog/add-redis-database-dialog.ts | 305 ++++++++++++++++ .../pageObjects/my-redis-databases-page.ts | 345 ++++++++++++++++++ .../pageObjects/user-agreement-dialog.ts | 2 +- 8 files changed, 979 insertions(+), 109 deletions(-) create mode 100644 tests/playwright/pageObjects/base-overview-page.ts create mode 100644 tests/playwright/pageObjects/components/common/toast.ts create mode 100644 tests/playwright/pageObjects/dialog/add-redis-database-dialog.ts create mode 100755 tests/playwright/pageObjects/my-redis-databases-page.ts diff --git a/tests/playwright/helpers/common.ts b/tests/playwright/helpers/common.ts index 64410dbd6c..90c8c8493a 100644 --- a/tests/playwright/helpers/common.ts +++ b/tests/playwright/helpers/common.ts @@ -1,10 +1,10 @@ -import { ClientFunction, RequestMock, t } from 'testcafe' +// import { ClientFunction, RequestMock, t } from 'testcafe' -import * as path from 'path' -import * as fs from 'fs' -import * as fsp from 'fs/promises' +// import * as path from 'path' +// import * as fs from 'fs' +// import * as fsp from 'fs/promises' import { faker } from '@faker-js/faker' -import { apiUrl } from './conf' +// import { apiUrl } from './conf' // const archiver = require('archiver') // diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index edc2468ea0..0bc28a5589 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -1,28 +1,28 @@ // import { Selector, t } from 'testcafe'; +import { DatabaseAPIRequests } from './api/api-database' +import { RedisOverviewPage } from './constants' +import { updateControlNumber } from './insights' import { AddNewDatabaseParameters, SentinelParameters, OSSClusterParameters -} from '../pageObjects/dialogs/add-redis-database-dialog'; -import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page'; +} from '../pageObjects/dialogs/add-redis-database-dialog' +import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' import { MyRedisDatabasePage, BrowserPage, AutoDiscoverREDatabases -} from '../pageObjects'; -import { UserAgreementDialog } from '../pageObjects/dialogs'; -import { DatabaseAPIRequests } from './api/api-database'; -import { RedisOverviewPage } from './constants'; -import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page'; -import { updateControlNumber } from './insights'; +} from '../pageObjects' +import { UserAgreementDialog } from '../pageObjects/dialogs' +import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' -const myRedisDatabasePage = new MyRedisDatabasePage(); -const discoverMasterGroupsPage = new DiscoverMasterGroupsPage(); -const autoDiscoverREDatabases = new AutoDiscoverREDatabases(); -const browserPage = new BrowserPage(); -const userAgreementDialog = new UserAgreementDialog(); -const databaseAPIRequests = new DatabaseAPIRequests(); -const rdiInstancesListPage = new RdiInstancesListPage(); +const myRedisDatabasePage = new MyRedisDatabasePage() +const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() +const autoDiscoverREDatabases = new AutoDiscoverREDatabases() +const browserPage = new BrowserPage() +const userAgreementDialog = new UserAgreementDialog() +const databaseAPIRequests = new DatabaseAPIRequests() +const rdiInstancesListPage = new RdiInstancesListPage() export class DatabaseHelper { /** @@ -35,7 +35,7 @@ export class DatabaseHelper { // Fill the add database form await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( databaseParameters - ); + ) // Click for saving await t .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) @@ -47,7 +47,7 @@ export class DatabaseHelper { ) .ok('The database not displayed', { timeout: 10000 }) // Close message - .click(myRedisDatabasePage.Toast.toastCloseButton); + .click(myRedisDatabasePage.Toast.toastCloseButton) } /** @@ -60,7 +60,7 @@ export class DatabaseHelper { // Fill sentinel parameters to auto-discover Master Groups await myRedisDatabasePage.AddRedisDatabaseDialog.discoverSentinelDatabases( databaseParameters - ); + ) // Click for autodiscover await t .click( @@ -70,10 +70,10 @@ export class DatabaseHelper { .expect(discoverMasterGroupsPage.addPrimaryGroupButton.exists) .ok('User is not on the second step of Sentinel flow', { timeout: 10000 - }); + }) // Select Master Groups and Add to Redis Insight - await discoverMasterGroupsPage.addMasterGroups(); - await t.click(autoDiscoverREDatabases.viewDatabasesButton); + await discoverMasterGroupsPage.addMasterGroups() + await t.click(autoDiscoverREDatabases.viewDatabasesButton) } /** @@ -86,7 +86,7 @@ export class DatabaseHelper { // Fill the add database form await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverREClusterDatabase( databaseParameters - ); + ) // Click on submit button await t .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) @@ -104,7 +104,7 @@ export class DatabaseHelper { .click(autoDiscoverREDatabases.databaseCheckbox) // Click Add selected databases button .click(autoDiscoverREDatabases.addSelectedDatabases) - .click(autoDiscoverREDatabases.viewDatabasesButton); + .click(autoDiscoverREDatabases.viewDatabasesButton) } /** @@ -117,7 +117,7 @@ export class DatabaseHelper { // Enter required parameters for OSS Cluster await myRedisDatabasePage.AddRedisDatabaseDialog.addOssClusterDatabase( databaseParameters - ); + ) // Click for saving await t .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) @@ -130,7 +130,7 @@ export class DatabaseHelper { databaseParameters.ossClusterDatabaseName ).exists ) - .ok('The database not displayed', { timeout: 10000 }); + .ok('The database not displayed', { timeout: 10000 }) } /** @@ -146,24 +146,24 @@ export class DatabaseHelper { await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverRECloudDatabase( cloudAPIAccessKey, cloudAPISecretKey - ); + ) await t.click( myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton - ); + ) await t .expect( autoDiscoverREDatabases.title.withExactText( 'Redis Cloud Subscriptions' ).exists ) - .ok('Subscriptions list not displayed', { timeout: 120000 }); + .ok('Subscriptions list not displayed', { timeout: 120000 }) // Select subscriptions - await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox); - await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton); + await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox) + await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton) // Select databases for adding - const databaseName = await autoDiscoverREDatabases.getDatabaseName(); - await t.click(autoDiscoverREDatabases.databaseCheckbox); - await t.click(autoDiscoverREDatabases.addSelectedDatabases); + const databaseName = await autoDiscoverREDatabases.getDatabaseName() + await t.click(autoDiscoverREDatabases.databaseCheckbox) + await t.click(autoDiscoverREDatabases.addSelectedDatabases) // Wait for database to be exist in the redis databases list await t .expect( @@ -171,11 +171,11 @@ export class DatabaseHelper { 'Redis Enterprise Databases Added' ).exists ) - .ok('Added databases list not displayed', { timeout: 20000 }); - await t.click(autoDiscoverREDatabases.viewDatabasesButton); + .ok('Added databases list not displayed', { timeout: 20000 }) + await t.click(autoDiscoverREDatabases.viewDatabasesButton) // uncomment when fixed db will be added to cloud subscription // await t.expect(myRedisDatabasePage.dbNameList.withExactText(databaseName).exists).ok('The database not displayed', { timeout: 10000 }); - return databaseName; + return databaseName } /** @@ -186,12 +186,12 @@ export class DatabaseHelper { async acceptLicenseTermsAndAddDatabase( databaseParameters: AddNewDatabaseParameters ): Promise { - await this.acceptLicenseTerms(); - await this.addNewStandaloneDatabase(databaseParameters); + await this.acceptLicenseTerms() + await this.addNewStandaloneDatabase(databaseParameters) // Connect to DB await myRedisDatabasePage.clickOnDBByName( databaseParameters.databaseName! - ); + ) } /** @@ -202,16 +202,16 @@ export class DatabaseHelper { async acceptLicenseTermsAndAddDatabaseApi( databaseParameters: AddNewDatabaseParameters ): Promise { - await this.acceptLicenseTerms(); + await this.acceptLicenseTerms() await databaseAPIRequests.addNewStandaloneDatabaseApi( databaseParameters - ); + ) // Reload Page to see the new added database through api - await myRedisDatabasePage.reloadPage(); + await myRedisDatabasePage.reloadPage() // Connect to DB await myRedisDatabasePage.clickOnDBByName( databaseParameters.databaseName! - ); + ) } /** @@ -222,12 +222,12 @@ export class DatabaseHelper { async acceptLicenseTermsAndAddOSSClusterDatabase( databaseParameters: OSSClusterParameters ): Promise { - await this.acceptLicenseTerms(); - await this.addOSSClusterDatabase(databaseParameters); + await this.acceptLicenseTerms() + await this.addOSSClusterDatabase(databaseParameters) // Connect to DB await myRedisDatabasePage.clickOnDBByName( databaseParameters.ossClusterDatabaseName! - ); + ) } /** @@ -237,16 +237,16 @@ export class DatabaseHelper { async acceptLicenseTermsAndAddSentinelDatabaseApi( databaseParameters: SentinelParameters ): Promise { - await this.acceptLicenseTerms(); + await this.acceptLicenseTerms() await databaseAPIRequests.discoverSentinelDatabaseApi( databaseParameters - ); + ) // Reload Page to see the database added through api - await myRedisDatabasePage.reloadPage(); + await myRedisDatabasePage.reloadPage() // Connect to DB await myRedisDatabasePage.clickOnDBByName( databaseParameters.masters![1].alias ?? '' - ); + ) } /** @@ -256,12 +256,12 @@ export class DatabaseHelper { async acceptLicenseTermsAndAddREClusterDatabase( databaseParameters: AddNewDatabaseParameters ): Promise { - await this.acceptLicenseTerms(); - await this.addNewREClusterDatabase(databaseParameters); + await this.acceptLicenseTerms() + await this.addNewREClusterDatabase(databaseParameters) // Connect to DB await myRedisDatabasePage.clickOnDBByName( databaseParameters.databaseName ?? '' - ); + ) } /** @@ -271,41 +271,41 @@ export class DatabaseHelper { async acceptLicenseTermsAndAddRECloudDatabase( databaseParameters: AddNewDatabaseParameters ): Promise { - const searchTimeout = 60 * 1000; // 60 sec to wait database appearing + const searchTimeout = 60 * 1000 // 60 sec to wait database appearing const dbSelector = myRedisDatabasePage.dbNameList.withExactText( databaseParameters.databaseName ?? '' - ); - const startTime = Date.now(); + ) + const startTime = Date.now() - await this.acceptLicenseTerms(); + await this.acceptLicenseTerms() await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( databaseParameters - ); + ) // Click for saving await t.click( myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton - ); - await t.wait(3000); + ) + await t.wait(3000) // Reload page until db appears do { - await myRedisDatabasePage.reloadPage(); + await myRedisDatabasePage.reloadPage() } while ( !(await dbSelector.exists) && Date.now() - startTime < searchTimeout - ); + ) await t .expect( myRedisDatabasePage.dbNameList.withExactText( databaseParameters.databaseName ?? '' ).exists ) - .ok('The database not displayed', { timeout: 5000 }); + .ok('The database not displayed', { timeout: 5000 }) await myRedisDatabasePage.clickOnDBByName( databaseParameters.databaseName ?? '' - ); + ) await t .expect(browserPage.keysSummary.exists) - .ok('Key list not loaded', { timeout: 15000 }); + .ok('Key list not loaded', { timeout: 15000 }) } /** @@ -315,44 +315,44 @@ export class DatabaseHelper { async addRECloudDatabase( databaseParameters: AddNewDatabaseParameters ): Promise { - const searchTimeout = 60 * 1000; // 60 sec to wait database appearing + const searchTimeout = 60 * 1000 // 60 sec to wait database appearing const dbSelector = myRedisDatabasePage.dbNameList.withExactText( databaseParameters.databaseName ?? '' - ); - const startTime = Date.now(); + ) + const startTime = Date.now() await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( databaseParameters - ); + ) // Click for saving await t.click( myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton - ); - await t.wait(3000); + ) + await t.wait(3000) // Reload page until db appears do { - await myRedisDatabasePage.reloadPage(); + await myRedisDatabasePage.reloadPage() } while ( !(await dbSelector.exists) && Date.now() - startTime < searchTimeout - ); + ) await t .expect( myRedisDatabasePage.dbNameList.withExactText( databaseParameters.databaseName ?? '' ).exists ) - .ok('The database not displayed', { timeout: 5000 }); + .ok('The database not displayed', { timeout: 5000 }) } // Accept License terms async acceptLicenseTerms(): Promise { - await t.maximizeWindow(); - await userAgreementDialog.acceptLicenseTerms(); - await updateControlNumber(48.2); + await t.maximizeWindow() + await userAgreementDialog.acceptLicenseTerms() + await updateControlNumber(48.2) // Open default databases list tab if RDI opened if (await rdiInstancesListPage.addRdiInstanceButton.exists) { - await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase); + await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) } // TODO delete after releasing chatbot if (await myRedisDatabasePage.AddRedisDatabaseDialog.aiChatMessage.exists) { @@ -362,13 +362,13 @@ export class DatabaseHelper { // Accept License terms and connect to the RedisStack database async acceptLicenseAndConnectToRedisStack(): Promise { - await this.acceptLicenseTerms(); - //Connect to DB + await this.acceptLicenseTerms() + // Connect to DB await t .click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) .click( myRedisDatabasePage.AddRedisDatabaseDialog.connectToRedisStackButton - ); + ) } /** @@ -376,11 +376,11 @@ export class DatabaseHelper { * @param databaseName The database name */ async deleteDatabase(databaseName: string): Promise { - await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton); + await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) if ( await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists ) { - await this.deleteDatabaseByNameApi(databaseName); + await this.deleteDatabaseByNameApi(databaseName) } } @@ -389,11 +389,11 @@ export class DatabaseHelper { * @param databaseName The database name */ async deleteCustomDatabase(databaseName: string): Promise { - await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton); + await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) if ( await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists ) { - await myRedisDatabasePage.deleteDatabaseByName(databaseName); + await myRedisDatabasePage.deleteDatabaseByName(databaseName) } } @@ -408,10 +408,10 @@ export class DatabaseHelper { if ( await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists ) { - await this.acceptLicenseTermsAndAddDatabase(databaseParameters); + await this.acceptLicenseTermsAndAddDatabase(databaseParameters) } else { - await this.acceptLicenseAndConnectToRedisStack(); + await this.acceptLicenseAndConnectToRedisStack() } } @@ -422,15 +422,15 @@ export class DatabaseHelper { async clickOnEditDatabaseByName(databaseName: string): Promise { const databaseId = await databaseAPIRequests.getDatabaseIdByName( databaseName - ); + ) const databaseEditBtn = Selector( `[data-testid=edit-instance-${databaseId}]` - ); + ) await t .expect(databaseEditBtn.exists) - .ok(`"${databaseName}" database not displayed`); - await t.click(databaseEditBtn); + .ok(`"${databaseName}" database not displayed`) + await t.click(databaseEditBtn) } /** @@ -440,15 +440,15 @@ export class DatabaseHelper { async deleteDatabaseByNameApi(databaseName: string): Promise { const databaseId = await databaseAPIRequests.getDatabaseIdByName( databaseName - ); + ) const databaseDeleteBtn = Selector( `[data-testid=delete-instance-${databaseId}-icon]` - ); + ) await t .expect(databaseDeleteBtn.exists) - .ok(`"${databaseName}" database not displayed`); - await t.click(databaseDeleteBtn); - await t.click(myRedisDatabasePage.confirmDeleteButton); + .ok(`"${databaseName}" database not displayed`) + await t.click(databaseDeleteBtn) + await t.click(myRedisDatabasePage.confirmDeleteButton) } } diff --git a/tests/playwright/pageObjects/base-overview-page.ts b/tests/playwright/pageObjects/base-overview-page.ts new file mode 100644 index 0000000000..f8fc59d120 --- /dev/null +++ b/tests/playwright/pageObjects/base-overview-page.ts @@ -0,0 +1,183 @@ +import { expect, Locator, Page } from '@playwright/test' +import { Toast } from './components/common/toast' +import BasePage from './base-page' +import { RedisOverviewPage } from '../helpers/constants' +import { DatabaseAPIRequests } from '../helpers/api/api-database' + +export type DatabasesForImport = { + host?: string, + port?: number | string, + name?: string, + result?: string, + username?: string, + auth?: string, + cluster?: boolean | string, + indName?: string, + db?: number, + ssh_port?: number, + timeout_connect?: number, + timeout_execute?: number, + other_field?: string, + ssl?: boolean, + ssl_ca_cert_path?: string, + ssl_local_cert_path?: string, + ssl_private_key_path?: string +}[] + +export class BaseOverviewPage extends BasePage { + // Component instance used in methods + private readonly toast: Toast + + // BUTTONS & ACTION SELECTORS + private readonly deleteRowButton: Locator + + private readonly confirmDeleteButton: Locator + + private readonly confirmDeleteAllDbButton: Locator + + // TABLE / LIST SELECTORS + private readonly instanceRow: Locator + + private readonly selectAllCheckbox: Locator + + private readonly deleteButtonInPopover: Locator + + private readonly dbNameList: Locator + + private readonly tableRowContent: Locator + + private readonly editDatabaseButton: Locator + + // NAVIGATION SELECTORS + private readonly databasePageLink: Locator + + private readonly rdiPageLink: Locator + + // Additional – used for deletion by name + private readonly deleteDatabaseButton: Locator + + // MODULE + private readonly moduleTooltip: Locator + + // API instance if needed in child pages (unused in these methods but may be used externally) + protected readonly databaseAPIRequests: DatabaseAPIRequests + + constructor(page: Page) { + super(page) + this.databaseAPIRequests = new DatabaseAPIRequests() + this.toast = new Toast(page) + + // BUTTONS & ACTION SELECTORS + this.deleteRowButton = page.locator('[data-testid^="delete-instance-"]') + this.confirmDeleteButton = page.locator('[data-testid^="delete-instance-"]', { hasText: 'Remove' }) + this.confirmDeleteAllDbButton = page.getByTestId('delete-selected-dbs') + + // TABLE / LIST SELECTORS + this.instanceRow = page.locator('[class*=euiTableRow-isSelectable]') + this.selectAllCheckbox = page.locator('[data-test-subj="checkboxSelectAll"]') + this.deleteButtonInPopover = page.locator('#deletePopover button') + this.dbNameList = page.locator('[data-testid^="instance-name"]') + this.tableRowContent = page.locator('[data-test-subj="database-alias-column"]') + this.editDatabaseButton = page.locator('[data-testid^="edit-instance"]') + + // NAVIGATION SELECTORS + this.databasePageLink = page.getByTestId('home-tab-databases') + this.rdiPageLink = page.getByTestId('home-tab-rdi-instances') + + // Additional – we alias deleteDatabaseButton to the same as deleteRowButton + this.deleteDatabaseButton = page.locator('[data-testid^="delete-instance-"]') + + // MODULE + this.moduleTooltip = page.locator('.euiToolTipPopover') + } + + async reloadPage(): Promise { + await this.page.reload() + } + + async setActivePage(type: RedisOverviewPage): Promise { + if (type === RedisOverviewPage.Rdi) { + await this.rdiPageLink.click() + } else { + await this.databasePageLink.click() + } + } + + async deleteAllInstance(): Promise { + const count = await this.instanceRow.count() + if (count > 1) { + await this.selectAllCheckbox.click() + await this.deleteButtonInPopover.click() + await this.confirmDeleteAllDbButton.click() + } else if (count === 1) { + await this.deleteDatabaseButton.click() + await this.confirmDeleteButton.click() + } + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + } + + async deleteDatabaseByName(dbName: string): Promise { + const count = await this.tableRowContent.count() + for (let i = 0; i < count; i++) { + const text = (await this.tableRowContent.nth(i).textContent()) || '' + if (text.includes(dbName)) { + // Assumes that the delete button for the row is located at index i-1. + await this.deleteRowButton.nth(i - 1).click() + await this.confirmDeleteButton.click() + break + } + } + } + + async clickOnDBByName(dbName: string): Promise { + const db = this.dbNameList.filter({ hasText: dbName.trim() }) + await expect(db).toBeVisible({ timeout: 10000 }) + await db.first().click() + } + + async clickOnEditDBByName(databaseName: string): Promise { + const count = await this.dbNameList.count() + for (let i = 0; i < count; i++) { + const text = (await this.dbNameList.nth(i).textContent()) || '' + if (text.includes(databaseName)) { + await this.editDatabaseButton.nth(i).click() + break + } + } + } + + async checkModulesInTooltip(moduleNameList: string[]): Promise { + for (const item of moduleNameList) { + await expect(this.moduleTooltip.locator('span', { hasText: `${item} v.` })).toBeVisible() + } + } + + async checkModulesOnPage(moduleList: Locator[]): Promise { + for (const item of moduleList) { + await expect(item).toBeVisible() + } + } + + async getAllDatabases(): Promise { + const databases: string[] = [] + await expect(this.dbNameList).toBeVisible() + const n = await this.dbNameList.count() + for (let k = 0; k < n; k++) { + const name = await this.dbNameList.nth(k).textContent() + databases.push(name || '') + } + return databases + } + + async compareInstances(actualList: string[], sortedList: string[]): Promise { + for (let k = 0; k < actualList.length; k++) { + await expect(actualList[k].trim()).toEqual(sortedList[k].trim()) + } + } + + getDatabaseNamesFromListByResult(listOfDb: DatabasesForImport, result: string): string[] { + return listOfDb.filter(element => element.result === result).map(item => item.name!) + } +} diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 8ee48e1cc6..3c1d1d138f 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -1,4 +1,4 @@ -import { Page } from '@playwright/test' +import {Locator, Page} from '@playwright/test' export default class BasePage { protected page: Page @@ -7,6 +7,10 @@ export default class BasePage { this.page = page } + async reload(): Promise { + await this.page.reload() + } + async navigateTo(url: string): Promise { await this.page.goto(url) } @@ -15,8 +19,8 @@ export default class BasePage { await this.page.goto('/') } - async click(selector: string): Promise { - await this.page.click(selector) + async click(locator: Locator): Promise { + await locator.click() } async fill(selector: string, value: string): Promise { @@ -27,11 +31,11 @@ export default class BasePage { return await this.page.textContent(selector) || '' } - async isVisible(selector: string): Promise { - return this.page.isVisible(selector) + async isVisible(locator: Locator): Promise { + return locator.isVisible() } - protected getByTestId(testId: string, options?: { timeout?: number }): Locator { - return this.page.getByTestId(testId, options) + async getByTestId(testId: string): Promise { + return this.page.getByTestId(testId) } } diff --git a/tests/playwright/pageObjects/components/common/toast.ts b/tests/playwright/pageObjects/components/common/toast.ts new file mode 100644 index 0000000000..e70fc00e50 --- /dev/null +++ b/tests/playwright/pageObjects/components/common/toast.ts @@ -0,0 +1,33 @@ +import { Locator, Page } from '@playwright/test' +import BasePage from "../../base-page"; + +export class Toast extends BasePage{ + private readonly toastHeader: Locator + private readonly toastBody: Locator + private readonly toastSuccess: Locator + private readonly toastError: Locator + private readonly toastCloseButton: Locator + private readonly toastSubmitBtn: Locator + private readonly toastCancelBtn: Locator + + constructor(page: Page) { + super(page) + this.toastHeader = page.getByTestId('euiToastHeader') + this.toastBody = page.locator('[class*=euiToastBody]') + this.toastSuccess = page.locator('[class*=euiToast--success]') + this.toastError = page.locator('[class*=euiToast--danger]') + this.toastCloseButton = page.getByTestId('toastCloseButton') + this.toastSubmitBtn = page.getByTestId('submit-tooltip-btn') + this.toastCancelBtn = page.getByTestId('toast-cancel-btn') + } + + + async isCloseButtonVisible(): Promise { + return this.isVisible(this.toastCloseButton) + } + + async closeToast(): Promise { + await this.toastCloseButton.click() + } + +} diff --git a/tests/playwright/pageObjects/dialog/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialog/add-redis-database-dialog.ts new file mode 100644 index 0000000000..eecc54e8a4 --- /dev/null +++ b/tests/playwright/pageObjects/dialog/add-redis-database-dialog.ts @@ -0,0 +1,305 @@ +import { expect, Locator, Page } from '@playwright/test' +import { TlsCertificates } from '../../helpers/constants' +import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel' + +export type AddNewDatabaseParameters = { + host: string + port: string + databaseName?: string + databaseUsername?: string + databasePassword?: string + // For OSS Cluster parameters, you might use these fields: + ossClusterHost?: string + ossClusterPort?: string + ossClusterDatabaseName?: string + caCert?: { + name?: string + certificate?: string + } + clientCert?: { + name?: string + certificate?: string + key?: string + } +} + +export type SentinelParameters = { + sentinelHost: string + sentinelPort: string + masters?: { + alias?: string + db?: string + name?: string + password?: string + }[] + sentinelPassword?: string + name?: string[] +} + +export type OSSClusterParameters = { + ossClusterHost: string + ossClusterPort: string + ossClusterDatabaseName: string +} + +export type SSHParameters = { + sshHost: string + sshPort: string + sshUsername: string + sshPassword?: string + sshPrivateKey?: string + sshPassphrase?: string +} + +export class AddRedisDatabaseDialog { + private readonly page: Page + private readonly redisCloudSigninPanel: RedisCloudSigninPanel + + // BUTTONS + private readonly addDatabaseButton: Locator + private readonly addRedisDatabaseButton: Locator + private readonly customSettingsButton: Locator + private readonly addAutoDiscoverDatabase: Locator + private readonly addCloudDatabaseButton: Locator + private readonly redisSoftwareButton: Locator + private readonly redisSentinelButton: Locator + + // TEXT INPUTS + private readonly hostInput: Locator + private readonly portInput: Locator + private readonly databaseAliasInput: Locator + private readonly passwordInput: Locator + private readonly usernameInput: Locator + private readonly accessKeyInput: Locator + private readonly secretKeyInput: Locator + private readonly databaseIndexInput: Locator + + // TABS + private readonly generalTab: Locator + private readonly securityTab: Locator + private readonly decompressionTab: Locator + + // DROPDOWNS + private readonly caCertField: Locator + private readonly clientCertField: Locator + private readonly selectCompressor: Locator + + // CHECKBOXES + private readonly databaseIndexCheckbox: Locator + private readonly useSSHCheckbox: Locator + + // RADIO BUTTONS + private readonly sshPasswordRadioBtn: Locator + private readonly sshPrivateKeyRadioBtn: Locator + + // LABELS + private readonly dataCompressorLabel: Locator + + // SSH TEXT INPUTS + private readonly sshHostInput: Locator + private readonly sshPortInput: Locator + private readonly sshUsernameInput: Locator + private readonly sshPasswordInput: Locator + private readonly sshPrivateKeyInput: Locator + private readonly sshPassphraseInput: Locator + + // OTHER + private readonly timeoutInput: Locator + + // For certificate removal + private trashIconMsk(certificate: TlsCertificates): string { + return `[data-testid^="delete-${certificate}-cert"]` + } + private getDeleteCertificate(certificate: TlsCertificates): Locator { + return this.page.locator(this.trashIconMsk(certificate)) + } + + constructor(page: Page) { + this.page = page + this.redisCloudSigninPanel = new RedisCloudSigninPanel(page) + + // BUTTONS + this.addDatabaseButton = page.locator('[data-testid^="add-redis-database"]') + this.addRedisDatabaseButton = page.getByTestId('btn-submit') + this.customSettingsButton = page.getByTestId('btn-connection-settings') + this.addAutoDiscoverDatabase = page.getByTestId('add-database_tab_software') + this.addCloudDatabaseButton = page.getByTestId('create-free-db-btn') + this.redisSoftwareButton = page.getByTestId('option-btn-software') + this.redisSentinelButton = page.getByTestId('option-btn-sentinel') + + // TEXT INPUTS + this.hostInput = page.getByTestId('host') + this.portInput = page.getByTestId('port') + this.databaseAliasInput = page.getByTestId('name') + this.passwordInput = page.getByTestId('password') + this.usernameInput = page.getByTestId('username') + this.accessKeyInput = page.getByTestId('access-key') + this.secretKeyInput = page.getByTestId('secret-key') + this.databaseIndexInput = page.getByTestId('db') + + // TABS + this.generalTab = page.getByTestId('manual-form-tab-general') + this.securityTab = page.getByTestId('manual-form-tab-security') + this.decompressionTab = page.getByTestId('manual-form-tab-decompression') + + // DROPDOWNS + this.caCertField = page.getByTestId('select-ca-cert') + this.clientCertField = page.getByTestId('select-cert') + this.selectCompressor = page.getByTestId('select-compressor') + + // CHECKBOXES + this.databaseIndexCheckbox = page.locator('[data-testid="showDb"] ~ div') + this.useSSHCheckbox = page.locator('[data-testid="use-ssh"] ~ div') + + // RADIO BUTTONS + this.sshPasswordRadioBtn = page.locator('#password ~ div') + this.sshPrivateKeyRadioBtn = page.locator('#privateKey ~ div') + + // LABELS + this.dataCompressorLabel = page.locator('[data-testid="showCompressor"] ~ label') + + // SSH TEXT INPUTS + this.sshHostInput = page.getByTestId('sshHost') + this.sshPortInput = page.getByTestId('sshPort') + this.sshUsernameInput = page.getByTestId('sshUsername') + this.sshPasswordInput = page.getByTestId('sshPassword') + this.sshPrivateKeyInput = page.getByTestId('sshPrivateKey') + this.sshPassphraseInput = page.getByTestId('sshPassphrase') + + // OTHER + this.timeoutInput = page.getByTestId('timeout') + } + + async addRedisDataBase(parameters: AddNewDatabaseParameters): Promise { + await expect(this.addDatabaseButton).toBeVisible({ timeout: 10000 }) + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + await this.hostInput.fill(parameters.host) + await this.portInput.fill(parameters.port) + await this.databaseAliasInput.fill(parameters.databaseName || '') + if (parameters.databaseUsername) { + await this.usernameInput.fill(parameters.databaseUsername) + } + if (parameters.databasePassword) { + await this.passwordInput.fill(parameters.databasePassword) + } + } + + async addLogicalRedisDatabase(parameters: AddNewDatabaseParameters, index: string): Promise { + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + await this.hostInput.fill(parameters.host) + await this.portInput.fill(parameters.port) + await this.databaseAliasInput.fill(parameters.databaseName || '') + if (parameters.databaseUsername) { + await this.usernameInput.fill(parameters.databaseUsername) + } + if (parameters.databasePassword) { + await this.passwordInput.fill(parameters.databasePassword) + } + await this.databaseIndexCheckbox.click() + await this.databaseIndexInput.fill(index) + await this.addRedisDatabaseButton.click() + } + + async addStandaloneSSHDatabase( + databaseParameters: AddNewDatabaseParameters, + sshParameters: SSHParameters + ): Promise { + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + await this.hostInput.fill(databaseParameters.host) + await this.portInput.fill(databaseParameters.port) + await this.databaseAliasInput.fill(databaseParameters.databaseName || '') + if (databaseParameters.databaseUsername) { + await this.usernameInput.fill(databaseParameters.databaseUsername) + } + if (databaseParameters.databasePassword) { + await this.passwordInput.fill(databaseParameters.databasePassword) + } + // Navigate to security tab and select SSH Tunnel checkbox + await this.securityTab.click() + await this.useSSHCheckbox.click() + // Fill SSH fields + await this.sshHostInput.fill(sshParameters.sshHost) + await this.sshPortInput.fill(sshParameters.sshPort) + await this.sshUsernameInput.fill(sshParameters.sshUsername) + if (sshParameters.sshPassword) { + await this.sshPasswordInput.fill(sshParameters.sshPassword) + } + if (sshParameters.sshPrivateKey) { + await this.sshPrivateKeyRadioBtn.click() + await this.sshPrivateKeyInput.fill(sshParameters.sshPrivateKey) + } + if (sshParameters.sshPassphrase) { + await this.sshPrivateKeyRadioBtn.click() + await this.sshPassphraseInput.fill(sshParameters.sshPassphrase) + } + await this.addRedisDatabaseButton.click() + } + + async discoverSentinelDatabases(parameters: SentinelParameters): Promise { + await this.addDatabaseButton.click() + await this.redisSentinelButton.click() + if (parameters.sentinelHost) { + await this.hostInput.fill(parameters.sentinelHost) + } + if (parameters.sentinelPort) { + await this.portInput.fill(parameters.sentinelPort) + } + if (parameters.sentinelPassword) { + await this.passwordInput.fill(parameters.sentinelPassword) + } + } + + async addAutodiscoverREClusterDatabase(parameters: AddNewDatabaseParameters): Promise { + await this.addDatabaseButton.click() + await this.redisSoftwareButton.click() + await this.hostInput.fill(parameters.host) + await this.portInput.fill(parameters.port) + await this.usernameInput.fill(parameters.databaseUsername || '') + await this.passwordInput.fill(parameters.databasePassword || '') + } + + async addAutodiscoverRECloudDatabase(cloudAPIAccessKey: string, cloudAPISecretKey: string): Promise { + await this.addDatabaseButton.click() + await this.addCloudDatabaseButton.click() + await this.accessKeyInput.fill(cloudAPIAccessKey) + await this.secretKeyInput.fill(cloudAPISecretKey) + } + + async addOssClusterDatabase(parameters: AddNewDatabaseParameters): Promise { + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + if (parameters.ossClusterHost) { + await this.hostInput.fill(parameters.ossClusterHost) + } + if (parameters.ossClusterPort) { + await this.portInput.fill(parameters.ossClusterPort) + } + if (parameters.ossClusterDatabaseName) { + await this.databaseAliasInput.fill(parameters.ossClusterDatabaseName) + } + } + + async setCompressorValue(compressor: string): Promise { + if (!(await this.selectCompressor.isVisible())) { + await this.dataCompressorLabel.click() + } + await this.selectCompressor.click() + await this.page.locator(`[id="${compressor}"]`).click() + } + + async removeCertificateButton(certificate: TlsCertificates, name: string): Promise { + await this.securityTab.click() + const row = this.page.locator('button').locator('div').filter({ hasText: name }) + const removeButtonFooter = this.page.locator('[class^="_popoverFooter"]') + if (certificate === TlsCertificates.CA) { + await this.caCertField.click() + } else { + await this.clientCertField.click() + } + await row.locator(this.trashIconMsk(certificate)).click() + await removeButtonFooter.locator(this.trashIconMsk(certificate)).click() + } +} diff --git a/tests/playwright/pageObjects/my-redis-databases-page.ts b/tests/playwright/pageObjects/my-redis-databases-page.ts new file mode 100755 index 0000000000..95eb6815ad --- /dev/null +++ b/tests/playwright/pageObjects/my-redis-databases-page.ts @@ -0,0 +1,345 @@ +import { expect, Locator, Page } from '@playwright/test' +// import { InsightsPanel } from './components/insights-panel' +import { BaseOverviewPage } from './base-overview-page' +import { Toast } from './components/common/toast' +// import { NavigationPanel } from './components/navigation-panel' +// import { NavigationHeader } from './components/navigation/navigation-header' +// import { AuthorizationDialog } from './dialogs/authorization-dialog' +// import { AddRedisDatabaseDialog } from './dialogs' +// import { DatabaseAPIRequests } from '../helpers/api/api-database' + +export type DatabasesForImport = { + host?: string, + port?: number | string, + name?: string, + result?: string, + username?: string, + auth?: string, + cluster?: boolean | string, + indName?: string, + db?: number, + ssh_port?: number, + timeout_connect?: number, + timeout_execute?: number, + other_field?: string, + ssl?: boolean, + ssl_ca_cert_path?: string, + ssl_local_cert_path?: string, + ssl_private_key_path?: string +}[] + +export class MyRedisDatabasePage extends BaseOverviewPage { + // // Component Instances + // private readonly navigationPanel: NavigationPanel + // + // private readonly addRedisDatabaseDialog: AddRedisDatabaseDialog + // + // private readonly insightsPanel: InsightsPanel + // + // private readonly navigationHeader: NavigationHeader + // + // private readonly authorizationDialog: AuthorizationDialog + // + // // API instance + // private readonly databaseAPIRequests: DatabaseAPIRequests + // + // // CSS Selectors + // private readonly cssNumberOfDbs: Locator + // + // private readonly cssRedisStackIcon: Locator + // + // // BUTTONS + // private readonly deleteDatabaseButton: Locator + // + // private readonly confirmDeleteButton: Locator + // + // private readonly deleteButtonInPopover: Locator + // + // private readonly confirmDeleteAllDbButton: Locator + // + // private readonly editDatabaseButton: Locator + // + // private readonly popoverHeader: Locator + // + // private readonly submitChangesButton: Locator + // + // private readonly promoButton: Locator + // + // private readonly sortByDatabaseAlias: Locator + // + // private readonly sortByHostAndPort: Locator + // + // private readonly sortByConnectionType: Locator + // + // private readonly importDatabasesBtn: Locator + // + // private readonly retryImportBtn: Locator + // + // private readonly removeImportedFileBtn: Locator + // + // private readonly exportBtn: Locator + // + // private readonly exportSelectedDbsBtn: Locator + // + // private readonly userProfileBtn: Locator + // + // private readonly closeImportBtn: Locator + // + // // CHECKBOXES + // private readonly selectAllCheckbox: Locator + // + // private readonly exportPasswordsCheckbox: Locator + // + // private readonly starFreeDbCheckbox: Locator + // + // // ICONS + // private readonly moduleColumn: Locator + // + // private readonly moduleSearchIcon: Locator + // + // private readonly moduleGraphIcon: Locator + // + // private readonly moduleJSONIcon: Locator + // + // private readonly moduleTimeseriesIcon: Locator + // + // private readonly moduleBloomIcon: Locator + // + // private readonly moduleAIIcon: Locator + // + // private readonly moduleGearsIcon: Locator + // + // private readonly redisStackIcon: Locator + // + // private readonly tooltipRedisStackLogo: Locator + // + // private readonly iconNotUsedDatabase: Locator + // + // private readonly iconDeletedDatabase: Locator + // + // // TEXT INPUTS + // private readonly searchInput: Locator + // + // private readonly importDatabaseInput: Locator + // + // // TEXT ELEMENTS + // private readonly moduleTooltip: Locator + // + // private readonly moduleQuantifier: Locator + // + private readonly dbNameList: Locator + // + // private readonly tableRowContent: Locator + // + // private readonly hostPort: Locator + // + // private readonly failedImportMessage: Locator + // + // private readonly importResult: Locator + // + // private readonly userProfileAccountInfo: Locator + // + // private readonly portCloudDb: Locator + // + // // DIALOG + // private readonly successResultsAccordion: Locator + // + // private readonly partialResultsAccordion: Locator + // + // private readonly failedResultsAccordion: Locator + // + // private readonly notificationUnusedDbMessage: Locator + // + // // CONTAINERS + // private readonly databaseContainer: Locator + // + // private readonly connectionTypeTitle: Locator + // + // private readonly addDatabaseImport: Locator + // + // // Assumed additional selector needed in deleteDatabaseByName method + // private readonly deleteRowButton: Locator + + constructor(page: Page) { + super(page) + // this.databaseAPIRequests = new DatabaseAPIRequests() + // + // // Initialize component instances + // this.navigationPanel = new NavigationPanel(page) + // this.addRedisDatabaseDialog = new AddRedisDatabaseDialog(page) + // this.insightsPanel = new InsightsPanel(page) + // this.navigationHeader = new NavigationHeader(page) + // this.authorizationDialog = new AuthorizationDialog(page) + // + // // CSS Selectors + // this.cssNumberOfDbs = page.getByTestId('number-of-dbs') + // this.cssRedisStackIcon = page.getByTestId('redis-stack-icon') + // + // // BUTTONS + // this.deleteDatabaseButton = page.locator('[data-testid^="delete-instance-"]') + // this.confirmDeleteButton = page.locator('[data-testid^="delete-instance-"]').filter({ hasText: 'Remove' }) + // this.deleteButtonInPopover = page.locator('#deletePopover button') + // this.confirmDeleteAllDbButton = page.getByTestId('delete-selected-dbs') + // this.editDatabaseButton = page.locator('[data-testid^="edit-instance"]') + // this.popoverHeader = page.locator('#formModalHeader') + // this.submitChangesButton = page.getByTestId('btn-submit') + // this.promoButton = page.getByTestId('promo-btn') + // this.sortByDatabaseAlias = page.locator('span[title="Database Alias"]') + // this.sortByHostAndPort = page.locator('span[title="Host:Port"]') + // this.sortByConnectionType = page.locator('span[title="Connection Type"]') + // this.importDatabasesBtn = page.getByTestId('option-btn-import') + // this.retryImportBtn = page.getByTestId('btn-retry') + // this.removeImportedFileBtn = page.locator('[aria-label="Clear selected files"]') + // this.exportBtn = page.getByTestId('export-btn') + // this.exportSelectedDbsBtn = page.getByTestId('export-selected-dbs') + // this.userProfileBtn = page.getByTestId('user-profile-btn') + // this.closeImportBtn = page.getByTestId('btn-close') + // + // // CHECKBOXES + // this.selectAllCheckbox = page.locator('[data-test-subj="checkboxSelectAll"]') + // this.exportPasswordsCheckbox = page.locator('[data-testid="export-passwords"] ~ div') + // this.starFreeDbCheckbox = page.locator('[data-test-subj="checkboxSelectRow-create-free-cloud-db"]') + // + // // ICONS + // this.moduleColumn = page.locator('[data-test-subj="tableHeaderCell_modules_3"]') + // this.moduleSearchIcon = page.locator('[data-testid^="Redis Query Engine"]') + // this.moduleGraphIcon = page.locator('[data-testid^="Graph"]') + // this.moduleJSONIcon = page.locator('[data-testid^="JSON"]') + // this.moduleTimeseriesIcon = page.locator('[data-testid^="Time Series"]') + // this.moduleBloomIcon = page.locator('[data-testid^="Probabilistic"]') + // this.moduleAIIcon = page.locator('[data-testid^="AI"]') + // this.moduleGearsIcon = page.locator('[data-testid^="Gears"]') + // this.redisStackIcon = page.getByTestId('redis-stack-icon') + // this.tooltipRedisStackLogo = page.getByTestId('tooltip-redis-stack-icon') + // this.iconNotUsedDatabase = page.locator('[data-testid^="database-status-tryDatabase-"]') + // this.iconDeletedDatabase = page.locator('[data-testid^="database-status-checkIfDeleted-"]') + // + // // TEXT INPUTS + // this.searchInput = page.getByTestId('search-database-list') + // this.importDatabaseInput = page.getByTestId('import-file-modal-filepicker') + // + // // TEXT ELEMENTS + // this.moduleTooltip = page.locator('.euiToolTipPopover') + // this.moduleQuantifier = page.getByTestId('_module') + this.dbNameList = page.locator('[data-testid^="instance-name"]') + // this.tableRowContent = page.locator('[data-test-subj="database-alias-column"]') + // this.hostPort = page.getByTestId('host-port') + // this.failedImportMessage = page.getByTestId('result-failed') + // this.importResult = page.locator('[data-testid^="table-result-"]') + // this.userProfileAccountInfo = page.locator('[data-testid^="profile-account-"]') + // this.portCloudDb = page.locator('[class*="column_host"]') + // + // // DIALOG + // this.successResultsAccordion = page.locator('[data-testid^="success-results-"]') + // this.partialResultsAccordion = page.locator('[data-testid^="partial-results-"]') + // this.failedResultsAccordion = page.locator('[data-testid^="failed-results-"]') + // this.notificationUnusedDbMessage = page.locator('[class^="_warningTooltipContent"]') + // + // // CONTAINERS + // this.databaseContainer = page.locator('.databaseContainer') + // this.connectionTypeTitle = page.locator('[data-test-subj="tableHeaderCell_connectionType_2"]') + // this.addDatabaseImport = page.getByTestId('add-db_import') + // + // // Additional property assumed for deleteDatabaseByName: + // this.deleteRowButton = page.locator('[data-testid^="delete-instance-"]') + } + + async clickOnDBByName(dbName: string): Promise { + const toast = new Toast(this.page) + if (await toast.isCloseButtonVisible()) { + await toast.closeToast() + } + const db = this.dbNameList.filter({ hasText: dbName.trim() }) + await expect(db).toBeVisible({ timeout: 10000 }) + await db.first().click() + } + + // async deleteAllDatabases(): Promise { + // await this.navigationPanel.myRedisDBButton.click() + // const dbNames = this.tableRowContent + // const count = await dbNames.count() + // if (count > 1) { + // await this.selectAllCheckbox.click() + // await this.deleteButtonInPopover.click() + // await this.confirmDeleteAllDbButton.click() + // } else if (count === 1) { + // await this.deleteDatabaseButton.click() + // await this.confirmDeleteButton.click() + // } + // if (await this.toast.toastCloseButton.isVisible()) { + // await this.toast.toastCloseButton.click() + // } + // } + // + // async deleteDatabaseByName(dbName: string): Promise { + // const dbNames = this.tableRowContent + // const count = await dbNames.count() + // for (let i = 0; i < count; i++) { + // const text = (await dbNames.nth(i).textContent()) || '' + // if (text.includes(dbName)) { + // // Assuming deleteRowButton corresponds to a delete button for the row, + // // and we click the one at index i-1 (as per original logic) + // await this.deleteRowButton.nth(i - 1).click() + // await this.confirmDeleteButton.click() + // break + // } + // } + // } + // + // async clickOnEditDBByName(databaseName: string): Promise { + // const dbNames = this.dbNameList + // const count = await dbNames.count() + // for (let i = 0; i < count; i++) { + // const text = (await dbNames.nth(i).textContent()) || '' + // if (text.includes(databaseName)) { + // await this.editDatabaseButton.nth(i).click() + // break + // } + // } + // } + // + // async checkModulesInTooltip(moduleNameList: string[]): Promise { + // for (const item of moduleNameList) { + // await expect(this.moduleTooltip.locator('span', { hasText: `${item} v.` })).toBeVisible() + // } + // } + // + // async checkModulesOnPage(moduleList: Locator[]): Promise { + // for (const item of moduleList) { + // await expect(item).toBeVisible() + // } + // } + // + // async getAllDatabases(): Promise { + // const databases: string[] = [] + // await expect(this.dbNameList).toBeVisible() + // const n = await this.dbNameList.count() + // for (let k = 0; k < n; k++) { + // const name = await this.dbNameList.nth(k).textContent() + // databases.push(name || '') + // } + // return databases + // } + // + // async compareDatabases(actualList: string[], sortedList: string[]): Promise { + // for (let k = 0; k < actualList.length; k++) { + // await expect(actualList[k].trim()).toEqual(sortedList[k].trim()) + // } + // } + // + // async verifyDatabaseStatusIsVisible(databaseName: string): Promise { + // const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) + // const databaseNewPoint = this.page.getByTestId(`database-status-new-${databaseId}`) + // await expect(databaseNewPoint).toBeVisible() + // } + // + // async verifyDatabaseStatusIsNotVisible(databaseName: string): Promise { + // const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) + // const databaseEditBtn = this.page.getByTestId(`database-status-new-${databaseId}`) + // await expect(databaseEditBtn).not.toBeVisible() + // } + // + // getDatabaseNamesFromListByResult(listOfDb: DatabasesForImport, result: string): string[] { + // return listOfDb.filter(element => element.result === result).map(item => item.name!) + // } +} diff --git a/tests/playwright/pageObjects/user-agreement-dialog.ts b/tests/playwright/pageObjects/user-agreement-dialog.ts index 746455a40f..d86e1d2386 100644 --- a/tests/playwright/pageObjects/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/user-agreement-dialog.ts @@ -31,6 +31,6 @@ export class UserAgreementDialog extends BasePage { } async getRecommendedSwitcherValue(): Promise { - return await this.recommendedSwitcher.getAttribute('aria-checked') + return this.recommendedSwitcher.getAttribute('aria-checked') } } From 4e16409e6fa3f7ac9520d4fb84b311f3b77c04b2 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 09:05:39 +0200 Subject: [PATCH 009/128] database created --- tests/playwright/.env.localChromium | 3 - tests/playwright/.localChromium.env | 2 + tests/playwright/fixtures/open-ri.ts | 23 +- tests/playwright/helpers/api/api-databases.ts | 156 ++ tests/playwright/helpers/api/http-client.ts | 63 + .../pageObjects/components/common/toast.ts | 2 +- .../add-redis-database-dialog.ts | 0 tests/playwright/playwright.config.ts | 9 +- .../playwright/rte/openvpn/docker-compose.yml | 13 + .../rte/openvpn/openvpn-data/conf/crl.pem | 11 + .../openvpn/openvpn-data/conf/openvpn.conf | 30 + .../conf/openvpn.conf.1636357834.bak | 30 + .../rte/openvpn/openvpn-data/conf/ovpn_env.sh | 25 + .../rte/openvpn/openvpn-data/conf/pki/ca.crt | 20 + .../139D258986D24CF7F2000F4365EA7CDE.pem | 87 + .../8055804ACAE0109030FB7947F31147A9.pem | 84 + .../rte/openvpn/openvpn-data/conf/pki/crl.pem | 11 + .../rte/openvpn/openvpn-data/conf/pki/dh.pem | 8 + .../openvpn/openvpn-data/conf/pki/index.txt | 2 + .../openvpn-data/conf/pki/index.txt.attr | 1 + .../openvpn-data/conf/pki/index.txt.attr.old | 1 + .../openvpn-data/conf/pki/index.txt.old | 1 + .../conf/pki/issued/localhost.crt | 87 + .../openvpn-data/conf/pki/issued/test.crt | 84 + .../openvpn-data/conf/pki/openssl-easyrsa.cnf | 138 ++ .../openvpn-data/conf/pki/private/ca.key | 30 + .../conf/pki/private/localhost.key | 28 + .../openvpn-data/conf/pki/private/test.key | 30 + .../openvpn-data/conf/pki/reqs/localhost.req | 15 + .../openvpn-data/conf/pki/reqs/test.req | 15 + .../openvpn-data/conf/pki/safessl-easyrsa.cnf | 138 ++ .../rte/openvpn/openvpn-data/conf/pki/serial | 1 + .../openvpn/openvpn-data/conf/pki/serial.old | 1 + .../rte/openvpn/openvpn-data/conf/pki/ta.key | 21 + tests/playwright/rte/openvpn/test.ovpn | 109 + .../rte/oss-cluster-7-rs/Dockerfile | 19 + .../rte/oss-cluster-7-rs/cluster-rs-create.sh | 12 + .../rte/oss-cluster-7-rs/creator.Dockerfile | 9 + .../rte/oss-cluster-7-rs/redis.conf | 1881 +++++++++++++++++ tests/playwright/rte/oss-cluster-7/Dockerfile | 11 + .../rte/oss-cluster-7/cluster-create.sh | 12 + .../rte/oss-cluster-7/creator.Dockerfile | 9 + tests/playwright/rte/oss-cluster-7/redis.conf | 1881 +++++++++++++++++ tests/playwright/rte/oss-sentinel/Dockerfile | 14 + .../playwright/rte/oss-sentinel/entrypoint.sh | 10 + .../playwright/rte/oss-sentinel/sentinel.conf | 15 + .../rte/oss-standalone-big/Dockerfile | 10 + .../rte/oss-standalone-big/entrypoint.sh | 9 + .../rte/oss-standalone-tls/Dockerfile | 13 + .../rte/oss-standalone-tls/certs/redis.crt | 28 + .../rte/oss-standalone-tls/certs/redis.key | 52 + .../rte/oss-standalone-tls/certs/redisCA.crt | 30 + .../rte/oss-standalone-tls/certs/user.crt | 28 + .../rte/oss-standalone-tls/certs/user.key | 52 + .../rte/redis-enterprise/Dockerfile | 9 + tests/playwright/rte/redis-enterprise/db.json | 6 + .../rte/redis-enterprise/entrypoint.sh | 42 + tests/playwright/rte/ssh/keys/pub/test.pub | 1 + tests/playwright/rte/ssh/keys/pub/testp.pub | 1 + tests/playwright/rte/ssh/keys/test | 7 + tests/playwright/rte/ssh/keys/testp | 8 + 61 files changed, 5434 insertions(+), 14 deletions(-) delete mode 100644 tests/playwright/.env.localChromium create mode 100644 tests/playwright/.localChromium.env create mode 100644 tests/playwright/helpers/api/api-databases.ts create mode 100644 tests/playwright/helpers/api/http-client.ts rename tests/playwright/pageObjects/{dialog => dialogs}/add-redis-database-dialog.ts (100%) create mode 100644 tests/playwright/rte/openvpn/docker-compose.yml create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old create mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key create mode 100644 tests/playwright/rte/openvpn/test.ovpn create mode 100644 tests/playwright/rte/oss-cluster-7-rs/Dockerfile create mode 100644 tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh create mode 100644 tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile create mode 100644 tests/playwright/rte/oss-cluster-7-rs/redis.conf create mode 100644 tests/playwright/rte/oss-cluster-7/Dockerfile create mode 100644 tests/playwright/rte/oss-cluster-7/cluster-create.sh create mode 100644 tests/playwright/rte/oss-cluster-7/creator.Dockerfile create mode 100644 tests/playwright/rte/oss-cluster-7/redis.conf create mode 100644 tests/playwright/rte/oss-sentinel/Dockerfile create mode 100644 tests/playwright/rte/oss-sentinel/entrypoint.sh create mode 100644 tests/playwright/rte/oss-sentinel/sentinel.conf create mode 100644 tests/playwright/rte/oss-standalone-big/Dockerfile create mode 100644 tests/playwright/rte/oss-standalone-big/entrypoint.sh create mode 100644 tests/playwright/rte/oss-standalone-tls/Dockerfile create mode 100644 tests/playwright/rte/oss-standalone-tls/certs/redis.crt create mode 100644 tests/playwright/rte/oss-standalone-tls/certs/redis.key create mode 100644 tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt create mode 100644 tests/playwright/rte/oss-standalone-tls/certs/user.crt create mode 100644 tests/playwright/rte/oss-standalone-tls/certs/user.key create mode 100644 tests/playwright/rte/redis-enterprise/Dockerfile create mode 100644 tests/playwright/rte/redis-enterprise/db.json create mode 100644 tests/playwright/rte/redis-enterprise/entrypoint.sh create mode 100644 tests/playwright/rte/ssh/keys/pub/test.pub create mode 100644 tests/playwright/rte/ssh/keys/pub/testp.pub create mode 100644 tests/playwright/rte/ssh/keys/test create mode 100644 tests/playwright/rte/ssh/keys/testp diff --git a/tests/playwright/.env.localChromium b/tests/playwright/.env.localChromium deleted file mode 100644 index 84139eb53f..0000000000 --- a/tests/playwright/.env.localChromium +++ /dev/null @@ -1,3 +0,0 @@ -BASE_URL=https://chrome.1111 -USERNAME=chromeUser -PASSWORD=chromePass diff --git a/tests/playwright/.localChromium.env b/tests/playwright/.localChromium.env new file mode 100644 index 0000000000..9b2ba99e08 --- /dev/null +++ b/tests/playwright/.localChromium.env @@ -0,0 +1,2 @@ +baseURL=https://localhost:5540 +apiUrl=https://localhost:5540/api diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 1018a9d35a..9ce6b37abc 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -1,14 +1,25 @@ import { test as base } from '@playwright/test' import BasePage from '../pageObjects/base-page' -import {UserAgreementDialog} from "../pageObjects/user-agreement-dialog"; +import {UserAgreementDialog} from '../pageObjects/user-agreement-dialog' +import {DatabaseAPIRequests} from'../helpers/api/api-databases' +import {apiUrl, ossStandaloneConfig} from "../helpers/conf"; type OpenRedisInsight = { - basePage: BasePage; - dialogUserAgreement: UserAgreementDialog; + basePage: BasePage + dialogUserAgreement: UserAgreementDialog + // dbAPI: DatabaseAPIRequests + apiUrl: string + } export const test = base.extend({ + apiUrl: ['default', { option: true }], + // dbAPI: async () => { + // const dbApi = new DatabaseAPIRequests(this.apiUrl) + // + // await dbApi.addNewStandaloneDatabaseApi(ossStandaloneConfig) + // }, // context: async ({ browser }, use) => { // const context = await browser.newContext() // await context.clearCookies() @@ -19,9 +30,13 @@ export const test = base.extend({ // }, // basePage: async ({ context }, use) => { basePage: async ({ page }, use) => { + // Set up the fixture. + // Add new database + const dbApi = new DatabaseAPIRequests(apiUrl) + await dbApi.addNewStandaloneDatabaseApi(ossStandaloneConfig) // const page = await context.newPage() - // Set up the fixture. + //Navigate to page const basePage = new BasePage(page) await basePage.navigateToHomeUrl() diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts new file mode 100644 index 0000000000..724e535291 --- /dev/null +++ b/tests/playwright/helpers/api/api-databases.ts @@ -0,0 +1,156 @@ +// import { sendPostRequest } from './api-common' +// import { AddNewDatabaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' +// import { ResourcePath } from '../constants' +// import {faker} from "@faker-js/faker" +// +// +// export class DatabaseAPIRequests { +// /** +// * Add a new Standalone database through api using host and port +// * @param databaseParameters The database parameters +// */ +// async addNewStandaloneDatabaseApi( +// databaseParameters: AddNewDatabaseParameters, isCloud = false +// ): Promise { +// const uniqueId = faker.string.alphanumeric({ length: 10 }) +// const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) +// const requestBody: { +// name?: string, +// host: string, +// port: number, +// username?: string, +// password?: string, +// tls?: boolean, +// verifyServerCert?: boolean, +// caCert?: { +// name: string, +// certificate?: string +// }, +// clientCert?: { +// name: string, +// certificate?: string, +// key?: string +// }, +// cloudDetails?: { +// cloudId: number, +// subscriptionType: string, +// planMemoryLimit: number, +// memoryLimitMeasurementUnit: string, +// free: boolean +// } +// } = { +// name: databaseParameters.databaseName, +// host: databaseParameters.host, +// port: Number(databaseParameters.port), +// username: databaseParameters.databaseUsername, +// password: databaseParameters.databasePassword +// } +// +// if (databaseParameters.caCert) { +// requestBody.tls = true +// requestBody.verifyServerCert = false +// requestBody.caCert = { +// name: `ca}-${uniqueId}`, +// certificate: databaseParameters.caCert.certificate +// } +// requestBody.clientCert = { +// name: `client}-${uniqueId}`, +// certificate: databaseParameters.clientCert!.certificate, +// key: databaseParameters.clientCert!.key +// } +// } +// +// if(isCloud) { +// requestBody.cloudDetails = { +// cloudId: uniqueIdNumber, +// subscriptionType: 'fixed', +// planMemoryLimit: 30, +// memoryLimitMeasurementUnit: 'mb', +// free: true +// } +// } +// const response = await sendPostRequest( +// ResourcePath.Databases, +// requestBody +// ) +// await t +// .expect(await response.body.name) +// .eql( +// databaseParameters.databaseName, +// `Database Name is not equal to ${databaseParameters.databaseName} in response` +// ) +// await t.expect(await response.status).eql(201) +// } +// +// } +import { faker } from '@faker-js/faker' +import { HttpClient } from './http-client' +import { AddNewDatabaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' +import { ResourcePath } from '../constants' + +export class DatabaseAPIRequests { + private httpClient: HttpClient + + constructor(baseURL: string) { + this.httpClient = new HttpClient(baseURL) + } + + /** + * Add a new standalone database using the HTTP client. + * @param databaseParameters The database parameters + * @param isCloud Whether the database is cloud-based + */ + async addNewStandaloneDatabaseApi( + databaseParameters: AddNewDatabaseParameters, + isCloud = false + ): Promise { + const uniqueId = faker.string.alphanumeric({ length: 10 }) + const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) + + const requestBody: any = { + name: databaseParameters.databaseName, + host: databaseParameters.host, + port: Number(databaseParameters.port), + username: databaseParameters.databaseUsername, + password: databaseParameters.databasePassword, + } + + if (databaseParameters.caCert && databaseParameters.clientCert) { + requestBody.tls = true + requestBody.verifyServerCert = false + requestBody.caCert = { + name: `ca-${uniqueId}`, + certificate: databaseParameters.caCert.certificate, + } + requestBody.clientCert = { + name: `client-${uniqueId}`, + certificate: databaseParameters.clientCert.certificate, + key: databaseParameters.clientCert.key, + } + } + + if (isCloud) { + requestBody.cloudDetails = { + cloudId: uniqueIdNumber, + subscriptionType: 'fixed', + planMemoryLimit: 100, + memoryLimitMeasurementUnit: 'mb', + free: true, + } + } + + const response = await this.httpClient.post(ResourcePath.Databases, requestBody) + + if (!response || Object.keys(response).length === 0) { + throw new Error('The response body is empty') + } + + if (response.name !== databaseParameters.databaseName) { + throw new Error( + `Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}` + ) + } + + console.log('Database created successfully:', response) + } +} diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts new file mode 100644 index 0000000000..7333bc7ce1 --- /dev/null +++ b/tests/playwright/helpers/api/http-client.ts @@ -0,0 +1,63 @@ +import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios' +import https from 'https' + +// Logging interceptor for requests +const requestLogger = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => { + console.log('Request:', config.method?.toUpperCase(), config.url) + console.log('Headers:', config.headers) + console.log('Body:', config.data) + return config +} + +// Logging interceptor for responses +const responseLogger = (response: AxiosResponse): AxiosResponse => { + console.log('Response:', response.status, response.data) + return response +} + +export class HttpClient { + private instance: AxiosInstance + + constructor(baseURL: string) { + this.instance = axios.create({ + baseURL, + httpsAgent: new https.Agent({ + rejectUnauthorized: false // allow self-signed or invalid certificates + }) + }) + + // Setup interceptors for logging + this.instance.interceptors.request.use( + requestLogger, + error => Promise.reject(error) + ) + this.instance.interceptors.response.use( + responseLogger, + error => Promise.reject(error) + ) + } + + async get(url: string, headers?: Record): Promise { + const response = await this.instance.get(url, { headers }) + if (!response.data) throw new Error('GET request returned empty response') + return response.data + } + + async post(url: string, body: unknown, headers?: Record): Promise { + const response = await this.instance.post(url, body, { headers }) + if (!response.data) throw new Error('Empty response body on POST request') + return response.data + } + + async put(url: string, body: unknown, headers?: Record): Promise { + const response = await this.instance.put(url, body, { headers }) + if (!response.data) throw new Error('Empty response body on PUT request') + return response.data + } + + async delete(url: string, headers?: Record): Promise { + const response = await this.instance.delete(url, { headers }) + if (!response.data) throw new Error('Empty response body from DELETE request') + return response.data + } +} diff --git a/tests/playwright/pageObjects/components/common/toast.ts b/tests/playwright/pageObjects/components/common/toast.ts index e70fc00e50..c150c522e4 100644 --- a/tests/playwright/pageObjects/components/common/toast.ts +++ b/tests/playwright/pageObjects/components/common/toast.ts @@ -1,5 +1,5 @@ import { Locator, Page } from '@playwright/test' -import BasePage from "../../base-page"; +import BasePage from '../../base-page' export class Toast extends BasePage{ private readonly toastHeader: Locator diff --git a/tests/playwright/pageObjects/dialog/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts similarity index 100% rename from tests/playwright/pageObjects/dialog/add-redis-database-dialog.ts rename to tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 0e34044cad..3c29ddfb58 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -2,11 +2,8 @@ import {defineConfig, devices } from '@playwright/test' import {Status} from 'allure-js-commons' import * as os from 'node:os' - -declare module '@playwright/test' { - interface PlaywrightTestOptions { - apiUrl?: string; // add your custom variables here - } +export type TestOptions = { + apiUrl: string; } /** @@ -20,7 +17,7 @@ declare module '@playwright/test' { /** * See https://playwright.dev/docs/test-configuration. */ -export default defineConfig({ +export default defineConfig({ testDir: './tests', /* Maximum time one test can run for. */ timeout: 300 * 1000, diff --git a/tests/playwright/rte/openvpn/docker-compose.yml b/tests/playwright/rte/openvpn/docker-compose.yml new file mode 100644 index 0000000000..f172a60226 --- /dev/null +++ b/tests/playwright/rte/openvpn/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.4" + +services: + openvpn: + cap_add: + - NET_ADMIN + image: kylemanna/openvpn + container_name: openvpn + ports: + - "1194:1194/udp" + restart: always + volumes: + - ./openvpn-data/conf:/etc/openvpn diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem new file mode 100644 index 0000000000..6dd7b13985 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBqDCBkQIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0Fw0yMTEx +MDgwNzQ5MTZaFw0zMTExMDYwNzQ5MTZaoE4wTDBKBgNVHSMEQzBBgBQ/AarQse++ +Argn1cHxvXoG8W+Pc6ETpBEwDzENMAsGA1UEAwwEdGVzdIIUcqHj2uS8cK9SYQiJ +moIR/7fU0PowDQYJKoZIhvcNAQELBQADggEBAD4eZq6iex+GdwiGaEGjtIcpQGJD +eQC9xUmkZSphKOcwjFyPC6/qOZ/MAgPhqzP2urgyrgeluvhFsISXmH/Di+6yVn7f +bV4c1e0CT6/H6filAelPnbkEclCv/48P6L3BN4o4S98QXzvMeF+YfpEYGyjO+/PW +vd0UH4mdtsDpk94Z2FKxeUey76EJPSvwa08dY+/CLynSGDXdGavErtFTBVoc5qcj +XdC3CI2ig3DnUUBvwiwSxiB5vzJ8Vhl3dxTag/4yYkfjmOS9EzdDRAhRfg7wsf+v +t4HmeQ2ntBkld1MtXJSKaOQa/if5+nNyb+4ktQkLC6YBd4SNpO8yYq/dMJA= +-----END X509 CRL----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf b/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf new file mode 100644 index 0000000000..1f58509e53 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf @@ -0,0 +1,30 @@ +server 192.168.255.0 255.255.255.0 +verb 3 +key /etc/openvpn/pki/private/localhost.key +ca /etc/openvpn/pki/ca.crt +cert /etc/openvpn/pki/issued/localhost.crt +dh /etc/openvpn/pki/dh.pem +tls-auth /etc/openvpn/pki/ta.key +key-direction 0 +keepalive 10 60 +persist-key +persist-tun + +proto udp +# Rely on Docker to do port mapping, internally always 1194 +port 1194 +dev tun0 +status /tmp/openvpn-status.log + +user nobody +group nogroup +comp-lzo no + +### Push Configurations Below +push "dhcp-option DNS 192.168.13.6" +push "comp-lzo no" +push "dhcp-option DOMAIN localhost" +push "route 192.168.13.0 255.255.255.0" +push "route 172.30.0.0 255.255.0.0" +push "route 172.31.0.0 255.255.0.0" +push "route 172.33.0.0 255.255.0.0" diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak b/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak new file mode 100644 index 0000000000..3f8e34df8e --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak @@ -0,0 +1,30 @@ +server 192.168.255.0 255.255.255.0 +verb 3 +key /etc/openvpn/pki/private/localhost.key +ca /etc/openvpn/pki/ca.crt +cert /etc/openvpn/pki/issued/localhost.crt +dh /etc/openvpn/pki/dh.pem +tls-auth /etc/openvpn/pki/ta.key +key-direction 0 +keepalive 10 60 +persist-key +persist-tun + +proto udp +# Rely on Docker to do port mapping, internally always 1194 +port 1194 +dev tun0 +status /tmp/openvpn-status.log + +user nobody +group nogroup +comp-lzo no + +### Route Configurations Below +route 192.168.254.0 255.255.255.0 + +### Push Configurations Below +push "block-outside-dns" +push "dhcp-option DNS 8.8.8.8" +push "dhcp-option DNS 8.8.4.4" +push "comp-lzo no" diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh b/tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh new file mode 100644 index 0000000000..9b3dabab03 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh @@ -0,0 +1,25 @@ +declare -x OVPN_AUTH= +declare -x OVPN_CIPHER= +declare -x OVPN_CLIENT_TO_CLIENT= +declare -x OVPN_CN=localhost +declare -x OVPN_COMP_LZO=0 +declare -x OVPN_DEFROUTE=0 +declare -x OVPN_DEVICE=tun +declare -x OVPN_DEVICEN=0 +declare -x OVPN_DISABLE_PUSH_BLOCK_DNS=1 +declare -x OVPN_DNS=1 +declare -x OVPN_DNS_SERVERS=([0]="192.168.13.6") +declare -x OVPN_ENV=/etc/openvpn/ovpn_env.sh +declare -x OVPN_EXTRA_CLIENT_CONFIG=() +declare -x OVPN_EXTRA_SERVER_CONFIG=() +declare -x OVPN_FRAGMENT= +declare -x OVPN_KEEPALIVE='10 60' +declare -x OVPN_MTU= +declare -x OVPN_NAT=1 +declare -x OVPN_PORT=1194 +declare -x OVPN_PROTO=udp +declare -x OVPN_PUSH=([0]="dhcp-option DOMAIN localhost" [1]="route 192.168.13.0 255.255.255.0" [2]="route 172.17.0.0 255.255.0.0") +declare -x OVPN_ROUTES=() +declare -x OVPN_SERVER=192.168.255.0/24 +declare -x OVPN_SERVER_URL=udp://localhost +declare -x OVPN_TLS_CIPHER= diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt new file mode 100644 index 0000000000..e43ba72148 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh6gAwIBAgIUcqHj2uS8cK9SYQiJmoIR/7fU0PowDQYJKoZIhvcNAQEL +BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzQ5MTBaFw0zMTExMDYwNzQ5 +MTBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCibt8kh9lqTC0O631rPHN0kMQ4kMQ/eZ59mKhAJZ3rBchIBrQne2yTw2z+ +X1ESa3VTkW2jyJ5r7iuo+Xyc8246tfBwO3u0DJ2DeZZOYPzMg48nJNxs3ur3iXAT +r6Aiwp0gtMNC2XcW7y5OPl8l+BhSt2PsWcdEdmLJgvRPJ2x+Ea8wivuw6FO6byK7 +Mxw7/CbNMw8Eey9eSz9kWDrgetS0kOgfqtt1ZnKDZkbLy8jFl0xW488VUrefUR1g +lOje8QySjDvzT8sUR0lASyS+/J6j/3gLlSS42e4SxMz00jEus+ye56cO16Pc+vKI +Xsev8cRPiSDTZTvc7Eaq/OcKVl11AgMBAAGjgYkwgYYwHQYDVR0OBBYEFD8BqtCx +774CuCfVwfG9egbxb49zMEoGA1UdIwRDMEGAFD8BqtCx774CuCfVwfG9egbxb49z +oROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCImaghH/t9TQ+jAMBgNV +HRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAXUrDhVAX +TkbhKRBuhUGQb03RyACQKBFM/SwhrmwpQMXo7BUuqWJ27U5/TRHrfKJxDgppmwIs +qmtrT07tA7e/OyFSZtZ9p/4H+5xM9FCsmu6YMQ3ZloHHGWmibrDNK70frVgRAEAS +FyAsEgpKZCr6OJNd7v2dbvO4AniZVVvccU17cJAx177YC3fNIuRtpHkm93D3qI+1 +4SED7rktVfXUKs6RMFmqIum5WRzgiJBAtk2GVQMrAAu/xmUPS/aqzstNte4KQ+UY +2qI9v1wYM8j+BT5nsBT02K+zOsYdkG39n7QEfcecPAjOkKsaFbSf/WZcsb6oCVgl +d/Nz24kfh76SqQ== +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem new file mode 100644 index 0000000000..723ee792b8 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 13:9d:25:89:86:d2:4c:f7:f2:00:0f:43:65:ea:7c:de + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=test + Validity + Not Before: Nov 8 07:49:15 2021 GMT + Not After : Feb 11 07:49:15 2024 GMT + Subject: CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:b8:d5:e2:66:d3:aa:e8:f7:2f:d8:76:b6:c6:67: + 5c:09:77:df:0b:1b:59:ca:f9:a9:fe:cc:50:91:91: + a4:2a:96:55:54:8c:a9:17:25:23:8d:93:76:05:5c: + 9e:86:68:82:22:42:52:f6:7d:72:5f:85:5c:7c:61: + d2:b2:a3:a2:5b:40:05:6f:eb:be:63:75:86:29:e7: + 97:e4:d7:20:1e:b4:c4:79:76:f7:cf:1d:70:ba:b0: + 10:ef:4e:9c:dc:15:4f:ee:b9:a7:b9:3f:f1:97:dd: + 77:0b:0e:3b:0b:c2:bd:b3:87:07:a4:95:2c:78:6b: + 7c:ac:7a:e4:02:c1:a0:3e:f5:ef:3a:51:f4:b3:4a: + 48:58:d0:16:10:8d:64:ba:a0:16:88:f0:62:55:fe: + 36:7b:9d:45:9f:f8:6d:e9:2a:1c:35:57:67:8e:2f: + 55:2f:27:87:dd:ce:df:a4:f3:9b:b5:80:7b:4a:f6: + 28:74:52:2d:cf:d9:ae:34:7f:6c:1d:89:f2:fc:00: + aa:1c:fa:a0:30:22:14:19:76:65:9c:31:60:39:5d: + 0d:0a:15:80:b2:26:44:69:73:a2:0d:11:c0:b5:21: + 6f:52:cd:4a:2f:87:23:48:28:fc:8c:db:83:83:56: + 7a:a5:63:61:4c:6c:bb:3b:80:9f:ba:ad:66:63:b0: + 63:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + DD:B7:0D:86:6B:E4:2F:30:5F:6C:C1:A8:A8:23:66:06:36:C4:30:BC + X509v3 Authority Key Identifier: + keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 + DirName:/CN=test + serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + DNS:localhost + Signature Algorithm: sha256WithRSAEncryption + 09:97:10:6a:b8:62:e7:e8:a5:a0:45:33:d2:85:0d:ca:61:73: + 8c:85:27:1d:3d:68:8b:65:55:4b:51:d9:86:a8:89:92:52:6b: + 98:4c:4b:75:05:ed:6e:e0:63:96:ce:44:b1:47:2a:71:32:32: + 86:f2:e3:5d:68:bd:82:1c:66:23:7a:ff:9e:e4:c3:a2:cd:79: + 2c:a0:63:9e:f5:cc:e4:71:60:0d:a5:69:5e:b5:c1:cb:4e:94: + 18:c5:f9:cd:89:c3:7a:33:4d:5b:6c:ec:9d:0c:0b:fe:72:72: + 07:b6:6d:ba:2b:10:e6:6e:0b:94:b6:3e:67:1a:c1:fe:73:e0: + dd:be:4c:1d:29:2b:01:fe:3e:ec:c6:d0:c8:de:04:77:ff:6a: + 7e:81:8f:86:1b:42:70:38:d1:47:cd:b9:11:33:9c:b2:7d:fa: + b4:5e:a2:a4:cd:0c:ed:3e:b1:28:f6:3d:6f:df:ea:34:83:b2: + fc:c1:31:28:75:02:fb:64:20:06:89:a9:31:ff:7c:0a:bc:c0: + aa:11:45:a4:e0:f4:98:cc:f7:77:21:de:41:34:32:97:3b:d7: + 88:58:47:7b:fb:c1:d2:9a:dc:5f:02:3f:4c:d9:99:71:f4:7b: + c8:31:c6:31:55:93:0e:42:28:b7:cb:43:e3:21:ce:84:de:0c: + a5:e1:7b:32 +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgIQE50liYbSTPfyAA9DZep83jANBgkqhkiG9w0BAQsFADAP +MQ0wCwYDVQQDDAR0ZXN0MB4XDTIxMTEwODA3NDkxNVoXDTI0MDIxMTA3NDkxNVow +FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAuNXiZtOq6Pcv2Ha2xmdcCXffCxtZyvmp/sxQkZGkKpZVVIypFyUjjZN2 +BVyehmiCIkJS9n1yX4VcfGHSsqOiW0AFb+u+Y3WGKeeX5NcgHrTEeXb3zx1wurAQ +706c3BVP7rmnuT/xl913Cw47C8K9s4cHpJUseGt8rHrkAsGgPvXvOlH0s0pIWNAW +EI1kuqAWiPBiVf42e51Fn/ht6SocNVdnji9VLyeH3c7fpPObtYB7SvYodFItz9mu +NH9sHYny/ACqHPqgMCIUGXZlnDFgOV0NChWAsiZEaXOiDRHAtSFvUs1KL4cjSCj8 +jNuDg1Z6pWNhTGy7O4Cfuq1mY7BjVwIDAQABo4GxMIGuMAkGA1UdEwQCMAAwHQYD +VR0OBBYEFN23DYZr5C8wX2zBqKgjZgY2xDC8MEoGA1UdIwRDMEGAFD8BqtCx774C +uCfVwfG9egbxb49zoROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCIma +ghH/t9TQ+jATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwFAYDVR0R +BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAJlxBquGLn6KWgRTPS +hQ3KYXOMhScdPWiLZVVLUdmGqImSUmuYTEt1Be1u4GOWzkSxRypxMjKG8uNdaL2C +HGYjev+e5MOizXksoGOe9czkcWANpWletcHLTpQYxfnNicN6M01bbOydDAv+cnIH +tm26KxDmbguUtj5nGsH+c+DdvkwdKSsB/j7sxtDI3gR3/2p+gY+GG0JwONFHzbkR +M5yyffq0XqKkzQztPrEo9j1v3+o0g7L8wTEodQL7ZCAGiakx/3wKvMCqEUWk4PSY +zPd3Id5BNDKXO9eIWEd7+8HSmtxfAj9M2Zlx9HvIMcYxVZMOQii3y0PjIc6E3gyl +4Xsy +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem new file mode 100644 index 0000000000..eda326fd95 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 80:55:80:4a:ca:e0:10:90:30:fb:79:47:f3:11:47:a9 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=test + Validity + Not Before: Nov 8 07:51:56 2021 GMT + Not After : Feb 11 07:51:56 2024 GMT + Subject: CN=test + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:cd:ba:3a:57:9b:0f:9b:dd:5a:c3:8c:ac:f0:24: + 2e:20:8e:3b:33:6d:86:b2:cb:81:00:83:4a:4f:16: + 40:cd:b3:e5:1d:c5:7f:98:e7:4b:a0:f5:6f:f3:5b: + 1b:a2:6b:16:4d:5c:5b:fe:46:c3:58:8e:0e:13:f9: + ec:69:68:37:f6:7d:e0:7e:8b:95:0f:71:ba:89:b1: + 5d:0e:ca:7c:9b:9e:07:57:c2:4b:e3:42:96:ef:5e: + 43:ea:fe:11:f2:38:3a:b4:0c:e3:e2:4c:28:e2:07: + bb:9a:56:63:98:88:91:15:f5:27:4d:a5:d1:88:0c: + 49:48:24:8f:71:8d:7d:0e:48:1b:d9:95:a4:7b:f2: + b7:f6:68:95:0c:14:2f:19:8d:ac:c5:cd:95:ac:42: + 93:ab:6e:60:33:40:90:f6:80:4e:a8:4b:f0:0f:d4: + d6:c0:5d:f2:8f:dd:c0:41:2b:78:96:12:60:37:e7: + c5:cc:ba:7a:36:de:0a:f0:e5:c9:90:51:3d:66:a6: + d1:b9:d2:b4:d3:ad:cb:72:f9:46:45:33:65:4a:e3: + e9:95:ee:23:37:92:b0:6b:a8:95:02:06:04:6b:7e: + 44:a9:4e:3c:fd:93:5b:32:4c:c3:40:24:9e:52:14: + d1:ac:aa:c5:88:4b:88:75:51:1c:96:26:c2:d7:75: + c6:7b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 8E:9E:25:9F:4F:53:6D:1D:D6:2C:08:03:2C:66:C3:6F:90:16:53:96 + X509v3 Authority Key Identifier: + keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 + DirName:/CN=test + serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA + + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha256WithRSAEncryption + 00:67:d2:93:e6:69:cb:fd:f6:9f:df:f6:59:20:2a:f6:0f:03: + ba:b3:da:65:de:62:23:36:a2:8d:4f:27:22:0e:3d:01:80:d9: + 59:cd:c5:f0:1a:9b:c5:e8:f4:6f:e2:c7:29:fc:37:21:2f:6f: + 9d:b8:8c:f6:6e:37:c9:b1:4a:0d:9d:e5:cd:0a:4b:01:0a:98: + 8f:46:e9:24:97:9c:ef:75:dd:a4:f7:33:7d:df:09:f3:4c:b6: + 3c:38:a7:2e:26:1d:68:f9:87:9a:ae:6c:60:d9:de:32:f1:69: + 66:97:cb:20:81:0d:b5:01:74:b5:73:8c:85:2b:5a:73:ea:cd: + e5:25:13:44:3a:24:0a:0a:72:4d:42:cc:0b:5a:c9:96:05:20: + 37:fb:1b:95:18:8d:66:ff:10:f8:3a:d8:03:6c:6c:37:6e:de: + 51:59:08:7e:d1:33:11:08:74:ed:fc:3f:4d:19:00:82:88:9f: + 95:66:a6:e9:f1:73:55:e1:7a:3f:ae:a6:e1:b7:51:df:92:28: + 19:42:1d:a5:a7:ed:b9:e4:00:ea:a7:55:e3:55:12:45:5f:f9: + e1:a5:1f:13:f1:ee:1a:31:e5:ae:9d:2e:ef:dd:d8:56:b2:7c: + f6:ba:08:41:db:13:16:31:0e:5d:41:b2:6d:98:01:e4:43:a2: + d1:34:9e:91 +-----BEGIN CERTIFICATE----- +MIIDRTCCAi2gAwIBAgIRAIBVgErK4BCQMPt5R/MRR6kwDQYJKoZIhvcNAQELBQAw +DzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzUxNTZaFw0yNDAyMTEwNzUxNTZa +MA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDNujpXmw+b3VrDjKzwJC4gjjszbYayy4EAg0pPFkDNs+UdxX+Y50ug9W/zWxui +axZNXFv+RsNYjg4T+expaDf2feB+i5UPcbqJsV0OynybngdXwkvjQpbvXkPq/hHy +ODq0DOPiTCjiB7uaVmOYiJEV9SdNpdGIDElIJI9xjX0OSBvZlaR78rf2aJUMFC8Z +jazFzZWsQpOrbmAzQJD2gE6oS/AP1NbAXfKP3cBBK3iWEmA358XMuno23grw5cmQ +UT1mptG50rTTrcty+UZFM2VK4+mV7iM3krBrqJUCBgRrfkSpTjz9k1syTMNAJJ5S +FNGsqsWIS4h1URyWJsLXdcZ7AgMBAAGjgZswgZgwCQYDVR0TBAIwADAdBgNVHQ4E +FgQUjp4ln09TbR3WLAgDLGbDb5AWU5YwSgYDVR0jBEMwQYAUPwGq0LHvvgK4J9XB +8b16BvFvj3OhE6QRMA8xDTALBgNVBAMMBHRlc3SCFHKh49rkvHCvUmEIiZqCEf+3 +1ND6MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0B +AQsFAAOCAQEAAGfSk+Zpy/32n9/2WSAq9g8DurPaZd5iIzaijU8nIg49AYDZWc3F +8Bqbxej0b+LHKfw3IS9vnbiM9m43ybFKDZ3lzQpLAQqYj0bpJJec73XdpPczfd8J +80y2PDinLiYdaPmHmq5sYNneMvFpZpfLIIENtQF0tXOMhStac+rN5SUTRDokCgpy +TULMC1rJlgUgN/sblRiNZv8Q+DrYA2xsN27eUVkIftEzEQh07fw/TRkAgoiflWam +6fFzVeF6P66m4bdR35IoGUIdpaftueQA6qdV41USRV/54aUfE/HuGjHlrp0u793Y +VrJ89roIQdsTFjEOXUGybZgB5EOi0TSekQ== +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem new file mode 100644 index 0000000000..6dd7b13985 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBqDCBkQIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0Fw0yMTEx +MDgwNzQ5MTZaFw0zMTExMDYwNzQ5MTZaoE4wTDBKBgNVHSMEQzBBgBQ/AarQse++ +Argn1cHxvXoG8W+Pc6ETpBEwDzENMAsGA1UEAwwEdGVzdIIUcqHj2uS8cK9SYQiJ +moIR/7fU0PowDQYJKoZIhvcNAQELBQADggEBAD4eZq6iex+GdwiGaEGjtIcpQGJD +eQC9xUmkZSphKOcwjFyPC6/qOZ/MAgPhqzP2urgyrgeluvhFsISXmH/Di+6yVn7f +bV4c1e0CT6/H6filAelPnbkEclCv/48P6L3BN4o4S98QXzvMeF+YfpEYGyjO+/PW +vd0UH4mdtsDpk94Z2FKxeUey76EJPSvwa08dY+/CLynSGDXdGavErtFTBVoc5qcj +XdC3CI2ig3DnUUBvwiwSxiB5vzJ8Vhl3dxTag/4yYkfjmOS9EzdDRAhRfg7wsf+v +t4HmeQ2ntBkld1MtXJSKaOQa/if5+nNyb+4ktQkLC6YBd4SNpO8yYq/dMJA= +-----END X509 CRL----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem new file mode 100644 index 0000000000..3b1acc9dd4 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAlG3e5COo1wC8aPTfb2ilsHyfPSj8WHMDMFfZYAiJsLAW6sNaA33L +9AfgsYDgb5CoHhil47Yrons97nvdC6lwVuG61Q7S77VX0MV6b12Gu+D8VJElnoNB +yQP/z6Frfg7OKCDelIfkvqYYPqQD33S7XR2a+7vO2E/vnc7vcfHozUUPKHqFtxyt +MNYuIs74l+2HHBHEO9fKWHc4IfHEkROQbehy0y6//qiKz/WqWAkQPX6eqgf26V23 +TyOT4UBvNv7nqOEpV4WS+zg+qH5c/kkcwSD/8jJMMi1cEWvz+9w+Kh7ponzQqOU0 +LOUysoYDcsZfuE/1SftOs44jUb4UCsKC4wIBAg== +-----END DH PARAMETERS----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt new file mode 100644 index 0000000000..7a38b10be0 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt @@ -0,0 +1,2 @@ +V 240211074915Z 139D258986D24CF7F2000F4365EA7CDE unknown /CN=localhost +V 240211075156Z 8055804ACAE0109030FB7947F31147A9 unknown /CN=test diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr new file mode 100644 index 0000000000..3a7e39e6ee --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr @@ -0,0 +1 @@ +unique_subject = no diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old new file mode 100644 index 0000000000..3a7e39e6ee --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old @@ -0,0 +1 @@ +unique_subject = no diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old new file mode 100644 index 0000000000..62a33d15e5 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old @@ -0,0 +1 @@ +V 240211074915Z 139D258986D24CF7F2000F4365EA7CDE unknown /CN=localhost diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt new file mode 100644 index 0000000000..723ee792b8 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 13:9d:25:89:86:d2:4c:f7:f2:00:0f:43:65:ea:7c:de + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=test + Validity + Not Before: Nov 8 07:49:15 2021 GMT + Not After : Feb 11 07:49:15 2024 GMT + Subject: CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:b8:d5:e2:66:d3:aa:e8:f7:2f:d8:76:b6:c6:67: + 5c:09:77:df:0b:1b:59:ca:f9:a9:fe:cc:50:91:91: + a4:2a:96:55:54:8c:a9:17:25:23:8d:93:76:05:5c: + 9e:86:68:82:22:42:52:f6:7d:72:5f:85:5c:7c:61: + d2:b2:a3:a2:5b:40:05:6f:eb:be:63:75:86:29:e7: + 97:e4:d7:20:1e:b4:c4:79:76:f7:cf:1d:70:ba:b0: + 10:ef:4e:9c:dc:15:4f:ee:b9:a7:b9:3f:f1:97:dd: + 77:0b:0e:3b:0b:c2:bd:b3:87:07:a4:95:2c:78:6b: + 7c:ac:7a:e4:02:c1:a0:3e:f5:ef:3a:51:f4:b3:4a: + 48:58:d0:16:10:8d:64:ba:a0:16:88:f0:62:55:fe: + 36:7b:9d:45:9f:f8:6d:e9:2a:1c:35:57:67:8e:2f: + 55:2f:27:87:dd:ce:df:a4:f3:9b:b5:80:7b:4a:f6: + 28:74:52:2d:cf:d9:ae:34:7f:6c:1d:89:f2:fc:00: + aa:1c:fa:a0:30:22:14:19:76:65:9c:31:60:39:5d: + 0d:0a:15:80:b2:26:44:69:73:a2:0d:11:c0:b5:21: + 6f:52:cd:4a:2f:87:23:48:28:fc:8c:db:83:83:56: + 7a:a5:63:61:4c:6c:bb:3b:80:9f:ba:ad:66:63:b0: + 63:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + DD:B7:0D:86:6B:E4:2F:30:5F:6C:C1:A8:A8:23:66:06:36:C4:30:BC + X509v3 Authority Key Identifier: + keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 + DirName:/CN=test + serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + DNS:localhost + Signature Algorithm: sha256WithRSAEncryption + 09:97:10:6a:b8:62:e7:e8:a5:a0:45:33:d2:85:0d:ca:61:73: + 8c:85:27:1d:3d:68:8b:65:55:4b:51:d9:86:a8:89:92:52:6b: + 98:4c:4b:75:05:ed:6e:e0:63:96:ce:44:b1:47:2a:71:32:32: + 86:f2:e3:5d:68:bd:82:1c:66:23:7a:ff:9e:e4:c3:a2:cd:79: + 2c:a0:63:9e:f5:cc:e4:71:60:0d:a5:69:5e:b5:c1:cb:4e:94: + 18:c5:f9:cd:89:c3:7a:33:4d:5b:6c:ec:9d:0c:0b:fe:72:72: + 07:b6:6d:ba:2b:10:e6:6e:0b:94:b6:3e:67:1a:c1:fe:73:e0: + dd:be:4c:1d:29:2b:01:fe:3e:ec:c6:d0:c8:de:04:77:ff:6a: + 7e:81:8f:86:1b:42:70:38:d1:47:cd:b9:11:33:9c:b2:7d:fa: + b4:5e:a2:a4:cd:0c:ed:3e:b1:28:f6:3d:6f:df:ea:34:83:b2: + fc:c1:31:28:75:02:fb:64:20:06:89:a9:31:ff:7c:0a:bc:c0: + aa:11:45:a4:e0:f4:98:cc:f7:77:21:de:41:34:32:97:3b:d7: + 88:58:47:7b:fb:c1:d2:9a:dc:5f:02:3f:4c:d9:99:71:f4:7b: + c8:31:c6:31:55:93:0e:42:28:b7:cb:43:e3:21:ce:84:de:0c: + a5:e1:7b:32 +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgIQE50liYbSTPfyAA9DZep83jANBgkqhkiG9w0BAQsFADAP +MQ0wCwYDVQQDDAR0ZXN0MB4XDTIxMTEwODA3NDkxNVoXDTI0MDIxMTA3NDkxNVow +FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAuNXiZtOq6Pcv2Ha2xmdcCXffCxtZyvmp/sxQkZGkKpZVVIypFyUjjZN2 +BVyehmiCIkJS9n1yX4VcfGHSsqOiW0AFb+u+Y3WGKeeX5NcgHrTEeXb3zx1wurAQ +706c3BVP7rmnuT/xl913Cw47C8K9s4cHpJUseGt8rHrkAsGgPvXvOlH0s0pIWNAW +EI1kuqAWiPBiVf42e51Fn/ht6SocNVdnji9VLyeH3c7fpPObtYB7SvYodFItz9mu +NH9sHYny/ACqHPqgMCIUGXZlnDFgOV0NChWAsiZEaXOiDRHAtSFvUs1KL4cjSCj8 +jNuDg1Z6pWNhTGy7O4Cfuq1mY7BjVwIDAQABo4GxMIGuMAkGA1UdEwQCMAAwHQYD +VR0OBBYEFN23DYZr5C8wX2zBqKgjZgY2xDC8MEoGA1UdIwRDMEGAFD8BqtCx774C +uCfVwfG9egbxb49zoROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCIma +ghH/t9TQ+jATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwFAYDVR0R +BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAJlxBquGLn6KWgRTPS +hQ3KYXOMhScdPWiLZVVLUdmGqImSUmuYTEt1Be1u4GOWzkSxRypxMjKG8uNdaL2C +HGYjev+e5MOizXksoGOe9czkcWANpWletcHLTpQYxfnNicN6M01bbOydDAv+cnIH +tm26KxDmbguUtj5nGsH+c+DdvkwdKSsB/j7sxtDI3gR3/2p+gY+GG0JwONFHzbkR +M5yyffq0XqKkzQztPrEo9j1v3+o0g7L8wTEodQL7ZCAGiakx/3wKvMCqEUWk4PSY +zPd3Id5BNDKXO9eIWEd7+8HSmtxfAj9M2Zlx9HvIMcYxVZMOQii3y0PjIc6E3gyl +4Xsy +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt new file mode 100644 index 0000000000..eda326fd95 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt @@ -0,0 +1,84 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 80:55:80:4a:ca:e0:10:90:30:fb:79:47:f3:11:47:a9 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=test + Validity + Not Before: Nov 8 07:51:56 2021 GMT + Not After : Feb 11 07:51:56 2024 GMT + Subject: CN=test + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:cd:ba:3a:57:9b:0f:9b:dd:5a:c3:8c:ac:f0:24: + 2e:20:8e:3b:33:6d:86:b2:cb:81:00:83:4a:4f:16: + 40:cd:b3:e5:1d:c5:7f:98:e7:4b:a0:f5:6f:f3:5b: + 1b:a2:6b:16:4d:5c:5b:fe:46:c3:58:8e:0e:13:f9: + ec:69:68:37:f6:7d:e0:7e:8b:95:0f:71:ba:89:b1: + 5d:0e:ca:7c:9b:9e:07:57:c2:4b:e3:42:96:ef:5e: + 43:ea:fe:11:f2:38:3a:b4:0c:e3:e2:4c:28:e2:07: + bb:9a:56:63:98:88:91:15:f5:27:4d:a5:d1:88:0c: + 49:48:24:8f:71:8d:7d:0e:48:1b:d9:95:a4:7b:f2: + b7:f6:68:95:0c:14:2f:19:8d:ac:c5:cd:95:ac:42: + 93:ab:6e:60:33:40:90:f6:80:4e:a8:4b:f0:0f:d4: + d6:c0:5d:f2:8f:dd:c0:41:2b:78:96:12:60:37:e7: + c5:cc:ba:7a:36:de:0a:f0:e5:c9:90:51:3d:66:a6: + d1:b9:d2:b4:d3:ad:cb:72:f9:46:45:33:65:4a:e3: + e9:95:ee:23:37:92:b0:6b:a8:95:02:06:04:6b:7e: + 44:a9:4e:3c:fd:93:5b:32:4c:c3:40:24:9e:52:14: + d1:ac:aa:c5:88:4b:88:75:51:1c:96:26:c2:d7:75: + c6:7b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 8E:9E:25:9F:4F:53:6D:1D:D6:2C:08:03:2C:66:C3:6F:90:16:53:96 + X509v3 Authority Key Identifier: + keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 + DirName:/CN=test + serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA + + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha256WithRSAEncryption + 00:67:d2:93:e6:69:cb:fd:f6:9f:df:f6:59:20:2a:f6:0f:03: + ba:b3:da:65:de:62:23:36:a2:8d:4f:27:22:0e:3d:01:80:d9: + 59:cd:c5:f0:1a:9b:c5:e8:f4:6f:e2:c7:29:fc:37:21:2f:6f: + 9d:b8:8c:f6:6e:37:c9:b1:4a:0d:9d:e5:cd:0a:4b:01:0a:98: + 8f:46:e9:24:97:9c:ef:75:dd:a4:f7:33:7d:df:09:f3:4c:b6: + 3c:38:a7:2e:26:1d:68:f9:87:9a:ae:6c:60:d9:de:32:f1:69: + 66:97:cb:20:81:0d:b5:01:74:b5:73:8c:85:2b:5a:73:ea:cd: + e5:25:13:44:3a:24:0a:0a:72:4d:42:cc:0b:5a:c9:96:05:20: + 37:fb:1b:95:18:8d:66:ff:10:f8:3a:d8:03:6c:6c:37:6e:de: + 51:59:08:7e:d1:33:11:08:74:ed:fc:3f:4d:19:00:82:88:9f: + 95:66:a6:e9:f1:73:55:e1:7a:3f:ae:a6:e1:b7:51:df:92:28: + 19:42:1d:a5:a7:ed:b9:e4:00:ea:a7:55:e3:55:12:45:5f:f9: + e1:a5:1f:13:f1:ee:1a:31:e5:ae:9d:2e:ef:dd:d8:56:b2:7c: + f6:ba:08:41:db:13:16:31:0e:5d:41:b2:6d:98:01:e4:43:a2: + d1:34:9e:91 +-----BEGIN CERTIFICATE----- +MIIDRTCCAi2gAwIBAgIRAIBVgErK4BCQMPt5R/MRR6kwDQYJKoZIhvcNAQELBQAw +DzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzUxNTZaFw0yNDAyMTEwNzUxNTZa +MA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDNujpXmw+b3VrDjKzwJC4gjjszbYayy4EAg0pPFkDNs+UdxX+Y50ug9W/zWxui +axZNXFv+RsNYjg4T+expaDf2feB+i5UPcbqJsV0OynybngdXwkvjQpbvXkPq/hHy +ODq0DOPiTCjiB7uaVmOYiJEV9SdNpdGIDElIJI9xjX0OSBvZlaR78rf2aJUMFC8Z +jazFzZWsQpOrbmAzQJD2gE6oS/AP1NbAXfKP3cBBK3iWEmA358XMuno23grw5cmQ +UT1mptG50rTTrcty+UZFM2VK4+mV7iM3krBrqJUCBgRrfkSpTjz9k1syTMNAJJ5S +FNGsqsWIS4h1URyWJsLXdcZ7AgMBAAGjgZswgZgwCQYDVR0TBAIwADAdBgNVHQ4E +FgQUjp4ln09TbR3WLAgDLGbDb5AWU5YwSgYDVR0jBEMwQYAUPwGq0LHvvgK4J9XB +8b16BvFvj3OhE6QRMA8xDTALBgNVBAMMBHRlc3SCFHKh49rkvHCvUmEIiZqCEf+3 +1ND6MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0B +AQsFAAOCAQEAAGfSk+Zpy/32n9/2WSAq9g8DurPaZd5iIzaijU8nIg49AYDZWc3F +8Bqbxej0b+LHKfw3IS9vnbiM9m43ybFKDZ3lzQpLAQqYj0bpJJec73XdpPczfd8J +80y2PDinLiYdaPmHmq5sYNneMvFpZpfLIIENtQF0tXOMhStac+rN5SUTRDokCgpy +TULMC1rJlgUgN/sblRiNZv8Q+DrYA2xsN27eUVkIftEzEQh07fw/TRkAgoiflWam +6fFzVeF6P66m4bdR35IoGUIdpaftueQA6qdV41USRV/54aUfE/HuGjHlrp0u793Y +VrJ89roIQdsTFjEOXUGybZgB5EOi0TSekQ== +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf new file mode 100644 index 0000000000..5c4fc79e7e --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf @@ -0,0 +1,138 @@ +# For use with Easy-RSA 3.0+ and OpenSSL or LibreSSL + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = $ENV::EASYRSA_PKI # Where everything is kept +certs = $dir # Where the issued certs are kept +crl_dir = $dir # Where the issued crl are kept +database = $dir/index.txt # database index file. +new_certs_dir = $dir/certs_by_serial # default place for new certs. + +certificate = $dir/ca.crt # The CA certificate +serial = $dir/serial # The current serial number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/ca.key # The private key +RANDFILE = $dir/.rand # private random number file + +x509_extensions = basic_exts # The extensions to add to the cert + +# This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA +# is designed for will. In return, we get the Issuer attached to CRLs. +crl_extensions = crl_ext + +default_days = $ENV::EASYRSA_CERT_EXPIRE # how long to certify for +default_crl_days= $ENV::EASYRSA_CRL_DAYS # how long before next CRL +default_md = $ENV::EASYRSA_DIGEST # use public key default MD +preserve = no # keep passed DN ordering + +# This allows to renew certificates which have not been revoked +unique_subject = no + +# A few different ways of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_anything + +# For the 'anything' policy, which defines allowed DN fields +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +name = optional +emailAddress = optional + +#################################################################### +# Easy-RSA request handling +# We key off $DN_MODE to determine how to format the DN +[ req ] +default_bits = $ENV::EASYRSA_KEY_SIZE +default_keyfile = privkey.pem +default_md = $ENV::EASYRSA_DIGEST +distinguished_name = $ENV::EASYRSA_DN +x509_extensions = easyrsa_ca # The extensions to add to the self signed cert + +# A placeholder to handle the $EXTRA_EXTS feature: +#%EXTRA_EXTS% # Do NOT remove or change this line as $EXTRA_EXTS support requires it + +#################################################################### +# Easy-RSA DN (Subject) handling + +# Easy-RSA DN for cn_only support: +[ cn_only ] +commonName = Common Name (eg: your user, host, or server name) +commonName_max = 64 +commonName_default = $ENV::EASYRSA_REQ_CN + +# Easy-RSA DN for org support: +[ org ] +countryName = Country Name (2 letter code) +countryName_default = $ENV::EASYRSA_REQ_COUNTRY +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = $ENV::EASYRSA_REQ_PROVINCE + +localityName = Locality Name (eg, city) +localityName_default = $ENV::EASYRSA_REQ_CITY + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = $ENV::EASYRSA_REQ_ORG + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = $ENV::EASYRSA_REQ_OU + +commonName = Common Name (eg: your user, host, or server name) +commonName_max = 64 +commonName_default = $ENV::EASYRSA_REQ_CN + +emailAddress = Email Address +emailAddress_default = $ENV::EASYRSA_REQ_EMAIL +emailAddress_max = 64 + +#################################################################### +# Easy-RSA cert extension handling + +# This section is effectively unused as the main script sets extensions +# dynamically. This core section is left to support the odd usecase where +# a user calls openssl directly. +[ basic_exts ] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always + +# The Easy-RSA CA extensions +[ easyrsa_ca ] + +# PKIX recommendations: + +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always + +# This could be marked critical, but it's nice to support reading by any +# broken clients who attempt to do so. +basicConstraints = CA:true + +# Limit key usage to CA tasks. If you really want to use the generated pair as +# a self-signed cert, comment this out. +keyUsage = cRLSign, keyCertSign + +# nsCertType omitted by default. Let's try to let the deprecated stuff die. +# nsCertType = sslCA + +# CRL extensions. +[ crl_ext ] + +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key new file mode 100644 index 0000000000..d47b74f89d --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,9953B10D522B006610527D745ED2B3FF + +xOtU092mS9pMlkXv5EZVQ3mAAnOPh/qb/0zmHagFTys1jHKKBGnXUWr8cYGD5Dyk +NVDoEn3RjAKhNV0Hvs18m/MUDqsuqj1fNw55LWszQ0/OioOA3+CiZlRd3qUmOFIZ +2oG+5l/k1XoH2S/zho4L53vxAMcTr62R+BXO++G4C1nMFwIchjpYInCoVZZblhP/ +Iue1lnYKkFhV609F5digBdM2mNIBJi5RST5T9rm0/EkxYNcfJgq9WMI4fN6ISXFm +KgxAT9+22v90KEGTb7ehNoczOZ7YS3kYC4zMoniIZ78E5Fv0d83strIa0S6rkKL+ +hGZ6KmVrtJpisdUIQ6Vv6aD5UCJfxdC/mC64F+gs0ElrYFC4t+Rb1uA2YyNgfuue +K0uAKcXPA4mpVyHd95TP5Yf332DiifnfO7/HqnULC+YQ+4Eo0zYJEfqbSbdI1hJD +gcneKrtRMIFL0ypzD9d8mGZce5hOHeJXR+xSWweMlQADYT/lmILHwUkShnSxa9em +S8ZgQNw56yIv1Ug3aBbtxxa/qYHWv11ZZeXA5vvkvXwt4lxmsFxYg8PySpoOqbE4 +XoJ4u5VeNS3EZZ2fHuDraTAkisfJoXRq+nYnNxK5Y+v03/y3/Ywj7UOSvH5b04Mu +lMiauOstDwlMZWsH+BqN70LWbddffZ3z2t6RdQbgxWHucMA4WB9EN18ynM3IEIM/ +HfiNVM5JwyZfsXULAq1bwCbkvbuuZDzjF0itBi7nZA6H+bdETP9neHUtHHhqW+zP +lQmQt4qq0JMkDNXQUt69p213iy8lTOnWzMhfLoTcqcJxFjUjujb3DUSOfcmFx8a8 +5sYaXbiSY8PbjWeX8S/dFx94Zwyy1pWj/rWiGVjeUjvxDzdfFyKf8JSSaXltCVvw +HoKaQFycuB0cuWBo+UxHRI+sUDj0rzdNSR1MxdCJbnWmOJBOoQCZWPcodt9ttSux +OxSNTU90bUMKRyAI9FevWXR7ZGx1o+VXPG0lGAD638eHIfBfc6RLqiYAyrDLOp8y +/yRvTCJAFIvSfqPOjXjDX9OI6KhNpsQRpLeEJrTG8DPDLvjTqb+yCimryIe0GpM2 +H8OGu9bQkUzbotQ/c1wPXWRGUgk0wJG5vwNE58y9QpkxZEkToXcE9DZ/SdyXd7YU +fA1V67Li0AfnBXAS921GHWvwH8di+OEpquy91ftv6Dwq429h2TFAqlPi3QpfGhgP +QQlmv5y5Wfi9SVqOTa/lz31NyJ2CVZpMdC+8vGZg1YTisss/ARYZtPBlcozYp9Op +ubovrEVugPzmzbTB8FxRuw+6GdCO0502B1k/32bvuV7XUMXltNJR094i0d/VqGeZ +WxdGMVSYn+32ai9YZeAVvfAisBjAVmG49iNpCAkL4a5s2ONbJ+H7AhhQadDmBoxA +JA4JNeV19LgSWUxfNE+8IMpoDewa8RQnr68VALZDIZ8TkJvMet2fC2xy86cSNTsJ +MNbIzVZ8d+ZazE1Ki6jaToX9i4a/fBhftG4Ssi9TlmkpPli+/JgHA6WOY58+ZiZ/ +pFht4WjYeayos9hRDwGrXqVJ/pQph5swTZIbvOkIkCtNRiDkHW4VLq8gVI8eNcxg +-----END RSA PRIVATE KEY----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key new file mode 100644 index 0000000000..4e185149a8 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC41eJm06ro9y/Y +drbGZ1wJd98LG1nK+an+zFCRkaQqllVUjKkXJSONk3YFXJ6GaIIiQlL2fXJfhVx8 +YdKyo6JbQAVv675jdYYp55fk1yAetMR5dvfPHXC6sBDvTpzcFU/uuae5P/GX3XcL +DjsLwr2zhweklSx4a3yseuQCwaA+9e86UfSzSkhY0BYQjWS6oBaI8GJV/jZ7nUWf ++G3pKhw1V2eOL1UvJ4fdzt+k85u1gHtK9ih0Ui3P2a40f2wdifL8AKoc+qAwIhQZ +dmWcMWA5XQ0KFYCyJkRpc6INEcC1IW9SzUovhyNIKPyM24ODVnqlY2FMbLs7gJ+6 +rWZjsGNXAgMBAAECggEAC47EQ23E6CBcy4pQz4MzDByEw+sH6FtIN/iKqS+UNl2E +JG9PO3VFDdtEq/0opHrweDfxfBxIBJUQPW7Yf/cfddlA/cid9RYqN4CVzOduyLzA +9F9uC3Np0yKjTHN7938VqnXNeRX4g2iDwxs2sfuRZSJyZeY2C6mc+CQPEeUNoiIf +iEevqy5uk08CcuZfT6mr/AM3EOhvtLc5Vz2jlDBR5DkkEibIzkiU9cBQqgfeoZee +aYy0SvowJ+YfeF+sq/Hj3XBozBfIvYrHSLjMduI2ZBLrNgC3rGzYZqNXCoRoEB9q +9par8S7bxvr8v0fPr+IgxOmYU3btOWdObD4WV8ZJYQKBgQDc9uIQwV8VcE+3oPn+ +bretrIn6gNSBpf5uHSN37Gj1SoZSA77hADRLb6S2nrEQe94ufA8wG7MbLW6n8QuA +K8mPKS8jNlYm3UVX58mvusAdl6aX5eVoJFwDnlOAr1QXxxS4elmkkg1a+LhGMLY1 +sYcBzFuCb4V7w5+1X8COVeWxqQKBgQDWJIBCuDDOGUDqP4xDz6g2h73Lprd5SqFW +S4wx+ANdW1KAqF8HC4F2wYtWOxNmhtSmcQDXoc46nAdc0K/ztt7DEaVIpmM0JkoB +UQd3jlDSSyxR2dqIbYX8G/orPspeDvDo8hZIy2c7Q6X0ECXeRKQT1QKLtlgGWbwx +uTjlLlyM/wKBgEgpjJzCOiZLPo2O0fYI/I2Iiqs3xa7tohEeDQZOO9zX0NQaCw16 +PpngRR+q5dKnUWFG/MLOWQjoJnpMW2ApbLMHVTCfvzUEEHBr708nozRG4Y4781a2 +gSJhY43kNIdradXxhzMay6fJEI1DvU39w0SQE9aeSy6nLZJTShRx8wi5AoGALYDY +GFVXWZQYLS6PuaEYl23EsM+eURHAsrue8MdIv7+2Cky81JxnSDrcRtiKyNCBw1Rj +324silu5XMmjeeAz+bN4MbXZgq1YMGttcMQ2/Fio9EOcojzTqbywLhxLmC0ImBdJ +EPLX3soDK5d61H41d1leDx4SGldbHB3Frz1owTUCgYBTW5pDgCdGKLWumNc89wyX +F0nGycd4/8sEJWozfgsQ+FmPlvqYjCke9nHTM6TIhTW9s6LxxmzIKIKIvcDPIMDR +B2zNldkuL7+tU5LSzEXi9rTVKGhYuVEC10sbaeWMLgtOxWe08TBZrTDW+Hec1BUT +tTqkU6SGHfgXMazbyF+DhA== +-----END PRIVATE KEY----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key new file mode 100644 index 0000000000..389dd93f5d --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIppK+RhgYEmsCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECG/rQJN9VfmpBIIEyMxNUosBZiWi +7ziO5LeP7tTeeNtTElo/BG7UgDfExEVyhfUvi/VF5yPNoGGzpIfSlUIdxs/IhkMA +qvBBgHGZUzHTrPcxPv3HFA99MQ3sOXbLxV28ilzt/IHyNb2XuP6IHqmgdOIKIdJq +0ln+WsToJLSJQy0wm56PJjcV5pnh+99wNQxnUO8N4PME+y1pkauBJ++i3y4t86Jn +hJJd2ViSnJQ6B4xe9+iaV1qxGNq/b35T7P38hUI3r5FmmX3k8OFScmpExuSJKkwy +G2PS5Q9V8jgYKCDHFqzdhRNMsfFOaeA4y6wlf8Vs8EvvFgwZuNWMZHLXrkLdsk/S +pmVSMQZB584+2y/LrUATKo72Whx3yXxNxBXLL3/vqHef2luCDQMgxVHnTEnczppA +2ABhgqXCz5/RwYiOL1ZIXrHpvL/j1UmCyZBA695hBNOLtSDwBmVeFaMZpfsQqu/h +624mbu/ifSYyXDtrd4XFISqSIEd1eAzXSQNS90UWzxQJQ13Yv57PBPnCvbUxWiuI +UhkHfgldYLvoswZ8B6Eenfw4b2EguFEksaYtrdiJ1H4F2EHP5i/ecXMFfBZ6f5+G +SDzkODUUnniIW3j6XQKRERWp5t1bSYopSWLI259ntmGHTe6zzRjCf4D2POJxA6cy +NDBXd1xxMZnjhDXO8y4vg8MIp4WhvNMvBAOS85mb2cFbL3eYZDokFe0XhTjEklai +YdLSTGa0aU2x5u6FDGulvSwBSuHknHFfk9ABOZ0bVpMr7pYWQ0M/lB+OdU+msbkx +liMVyrnsZjTfYhv/X8HQUKy087i7D6FNtcmNENkJKN7AAjnDaPiBizdMVWNSKrEa +ox+V1uWpXba0HFtl57V1c3FvuXYtpQ7vSk3BrJMtSkkWalWmCxE6df7nJeygG4tj +CskvgR75N6TOiNK6C8gKXZbirufICH47hJHzPbfDK+Wwl+tccXqL+5rcqGpqaHGw +ED3Y5QkqLuk0wCBiyyuEX4E+l/Is64dRaiahjqp2M/SA4zuk1kIhEPRGyN9W6jx4 +T3yocMdXq7N87O+gA5XW+TPZlDxZeGIJK2V1qWpuvdbBFbT/PEdXVbFhErEVs2Vp +KIxWGYxeajOruPdHBAbLtJaWiqqG84QCglbd240eG7fXse+onVHa9r0mhIgBdzOa +Keheq5Wrjf6Zk9ljnzCEl79Bg92ZTWwOmOsXAWVL0eHS9VOkuSfjIVhEP0D69fxE +dOeMB5ECy5vlnejkRnM0jtfaRuM+7ZNydKAL0z3gKjnBzYotNu8qcw/Pf/+c5qy3 +M1kZGeY/y+wdj+vHC9Tmi8dk21+ZEuZJ8Yb0kvv3lSo5UHdv8OxxQyVQdXc8M/Nx +ZiMo8lryBhNbK6AIguukcTUo7HXXLg7+M78H7VKq/syZJvBVU9RT0NU7Z+amFgh+ +YXQPnu5IUCPAxTIEU0f17GXMLLkp2rRNMLiJVGZmyD+BTkWhc7z+z4jgtR16gqUA +GBBzYI3ZlOekfNEKIemLYn3AaPAB79myaJxVsy/9KzNj2xv3r2ClAF0EWJLnhMrq +dqHqhxkqjTeDAfqJwNrxWJ7Wvp5I9uJ+a2wIyrY5gluhEcdiaw3ehB9/ykLAMvtQ +lpntxcSG4bJTbVuDW/JNRQ== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req new file mode 100644 index 0000000000..e13ce06215 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAuNXiZtOq6Pcv2Ha2xmdcCXffCxtZyvmp/sxQkZGk +KpZVVIypFyUjjZN2BVyehmiCIkJS9n1yX4VcfGHSsqOiW0AFb+u+Y3WGKeeX5Ncg +HrTEeXb3zx1wurAQ706c3BVP7rmnuT/xl913Cw47C8K9s4cHpJUseGt8rHrkAsGg +PvXvOlH0s0pIWNAWEI1kuqAWiPBiVf42e51Fn/ht6SocNVdnji9VLyeH3c7fpPOb +tYB7SvYodFItz9muNH9sHYny/ACqHPqgMCIUGXZlnDFgOV0NChWAsiZEaXOiDRHA +tSFvUs1KL4cjSCj8jNuDg1Z6pWNhTGy7O4Cfuq1mY7BjVwIDAQABoAAwDQYJKoZI +hvcNAQELBQADggEBAJ6ELpvR6jUrCnMqg+/CDeiWXVoB72tC9EQQxprQhRHxxcE+ +MzThuE9/bdC6UwJur+272Ih72/bPRCPdRO3H8338M7RQHW8CVA3lVrLNxjxehx4U +rmiSuZqo+BsRYkz3IYjndRe+1I5Dhbi5qrNcognuBg6yGnNAqsFQRTkoeR20smxt +h1WNsAT+p9a0gRo6MCtJRvzsZX3B4N1C+n18hfHOHiiG3gMHHWR61O4BKYbgImhb +B3e6mQox4a3a5tHNgJrO0Q1LMrfAml2VMv/Gmx46f6M7GWDKRifXiP0hyY8OVvrQ +uoTvWF2Fhas7TTRNeAE+m2Bpac8i8Umuf6OeOcs= +-----END CERTIFICATE REQUEST----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req new file mode 100644 index 0000000000..93d6d6bed8 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICVDCCATwCAQAwDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAM26OlebD5vdWsOMrPAkLiCOOzNthrLLgQCDSk8WQM2z5R3F +f5jnS6D1b/NbG6JrFk1cW/5Gw1iODhP57GloN/Z94H6LlQ9xuomxXQ7KfJueB1fC +S+NClu9eQ+r+EfI4OrQM4+JMKOIHu5pWY5iIkRX1J02l0YgMSUgkj3GNfQ5IG9mV +pHvyt/ZolQwULxmNrMXNlaxCk6tuYDNAkPaATqhL8A/U1sBd8o/dwEEreJYSYDfn +xcy6ejbeCvDlyZBRPWam0bnStNOty3L5RkUzZUrj6ZXuIzeSsGuolQIGBGt+RKlO +PP2TWzJMw0AknlIU0ayqxYhLiHVRHJYmwtd1xnsCAwEAAaAAMA0GCSqGSIb3DQEB +CwUAA4IBAQAzh0IYj8G2kv5a4I5gHONFN7X3tsYV+hTsr+Oi8/4RzYGzHKjN+VyR +73YaIAY2pQACuz6QBsx1Gd4EhGnobCAwHqRoad5fsa6LQorToEkF1ZNH7262zZzZ +mrG9Ke2MTIWbW1GQbnBj1/UAl1FAzF82zYrgCvNaM/B9HnALPqLTnP3I4Am+WGkm +/3yEfxI9RO56Rub5VeX/rW/6YORjrv0s7SgFB2ec+zG0zKSmQwx9H2QGL1GJhgMz +c+oCf+hEHMBVa2leQoDJHTUvLlT+gHf+RqB/FWGmeDWpXwj3wY+EUiS3HCLPwcPj +xQxQJZACPqFDaxSKyFH9JdfOojYqhmK+ +-----END CERTIFICATE REQUEST----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf new file mode 100644 index 0000000000..d42a92f42e --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf @@ -0,0 +1,138 @@ +# For use with Easy-RSA 3.0+ and OpenSSL or LibreSSL + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = /etc/openvpn/pki # Where everything is kept +certs = /etc/openvpn/pki # Where the issued certs are kept +crl_dir = /etc/openvpn/pki # Where the issued crl are kept +database = /etc/openvpn/pki/index.txt # database index file. +new_certs_dir = /etc/openvpn/pki/certs_by_serial # default place for new certs. + +certificate = /etc/openvpn/pki/ca.crt # The CA certificate +serial = /etc/openvpn/pki/serial # The current serial number +crl = /etc/openvpn/pki/crl.pem # The current CRL +private_key = /etc/openvpn/pki/private/ca.key # The private key +RANDFILE = /etc/openvpn/pki/.rand # private random number file + +x509_extensions = basic_exts # The extensions to add to the cert + +# This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA +# is designed for will. In return, we get the Issuer attached to CRLs. +crl_extensions = crl_ext + +default_days = 825 # how long to certify for +default_crl_days= 3650 # how long before next CRL +default_md = sha256 # use public key default MD +preserve = no # keep passed DN ordering + +# This allows to renew certificates which have not been revoked +unique_subject = no + +# A few different ways of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_anything + +# For the 'anything' policy, which defines allowed DN fields +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +name = optional +emailAddress = optional + +#################################################################### +# Easy-RSA request handling +# We key off $DN_MODE to determine how to format the DN +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +default_md = sha256 +distinguished_name = cn_only +x509_extensions = easyrsa_ca # The extensions to add to the self signed cert + +# A placeholder to handle the $EXTRA_EXTS feature: +#%EXTRA_EXTS% # Do NOT remove or change this line as $EXTRA_EXTS support requires it + +#################################################################### +# Easy-RSA DN (Subject) handling + +# Easy-RSA DN for cn_only support: +[ cn_only ] +commonName = Common Name (eg: your user, host, or server name) +commonName_max = 64 +commonName_default = ChangeMe + +# Easy-RSA DN for org support: +[ org ] +countryName = Country Name (2 letter code) +countryName_default = US +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = California + +localityName = Locality Name (eg, city) +localityName_default = San Francisco + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Copyleft Certificate Co + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = My Organizational Unit + +commonName = Common Name (eg: your user, host, or server name) +commonName_max = 64 +commonName_default = ChangeMe + +emailAddress = Email Address +emailAddress_default = me@example.net +emailAddress_max = 64 + +#################################################################### +# Easy-RSA cert extension handling + +# This section is effectively unused as the main script sets extensions +# dynamically. This core section is left to support the odd usecase where +# a user calls openssl directly. +[ basic_exts ] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always + +# The Easy-RSA CA extensions +[ easyrsa_ca ] + +# PKIX recommendations: + +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always + +# This could be marked critical, but it's nice to support reading by any +# broken clients who attempt to do so. +basicConstraints = CA:true + +# Limit key usage to CA tasks. If you really want to use the generated pair as +# a self-signed cert, comment this out. +keyUsage = cRLSign, keyCertSign + +# nsCertType omitted by default. Let's try to let the deprecated stuff die. +# nsCertType = sslCA + +# CRL extensions. +[ crl_ext ] + +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial new file mode 100644 index 0000000000..ae8764b339 --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial @@ -0,0 +1 @@ +8055804ACAE0109030FB7947F31147AA diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old new file mode 100644 index 0000000000..32e19cae7a --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old @@ -0,0 +1 @@ +8055804acae0109030fb7947f31147a9 diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key new file mode 100644 index 0000000000..c0d007b4fe --- /dev/null +++ b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key @@ -0,0 +1,21 @@ +# +# 2048 bit OpenVPN static key +# +-----BEGIN OpenVPN Static key V1----- +456226c4d5a6895c48dad7fd5d36ee57 +3eb280683fbbfe1699d63e9fd4e5ec5b +70500489c3ec36e0c30d6b18f9c48b6b +aede839a99d492fd26beb51317c08eb2 +ebb320a0b980da0b13a88e37559594f5 +03b21fa6d72548f7be5fdb41ad1de315 +82373a95c5c503c1101236f43a59ec68 +ddc9a83d4b4a4437f2db9e16bcbd433a +5211d060bc8376f1efe99bbf2413e543 +4e4473d5028c95f33ad5df3637505c31 +bb7661b03e7d882c3ec1c5ca5f9c2277 +09e2e4323392efb0dff0abadbe6d6887 +27bddf4a2f7f795fe7c227813f76cac2 +9e919074c638ad36e5001a187d113c4b +3faab93dde06734c15a198ad686a315a +3e1f91612528d4f6c4281916625e54b1 +-----END OpenVPN Static key V1----- diff --git a/tests/playwright/rte/openvpn/test.ovpn b/tests/playwright/rte/openvpn/test.ovpn new file mode 100644 index 0000000000..a6124a8815 --- /dev/null +++ b/tests/playwright/rte/openvpn/test.ovpn @@ -0,0 +1,109 @@ + +client +nobind +dev tun +remote-cert-tls server + +remote 127.0.0.1 1194 udp + + +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIppK+RhgYEmsCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECG/rQJN9VfmpBIIEyMxNUosBZiWi +7ziO5LeP7tTeeNtTElo/BG7UgDfExEVyhfUvi/VF5yPNoGGzpIfSlUIdxs/IhkMA +qvBBgHGZUzHTrPcxPv3HFA99MQ3sOXbLxV28ilzt/IHyNb2XuP6IHqmgdOIKIdJq +0ln+WsToJLSJQy0wm56PJjcV5pnh+99wNQxnUO8N4PME+y1pkauBJ++i3y4t86Jn +hJJd2ViSnJQ6B4xe9+iaV1qxGNq/b35T7P38hUI3r5FmmX3k8OFScmpExuSJKkwy +G2PS5Q9V8jgYKCDHFqzdhRNMsfFOaeA4y6wlf8Vs8EvvFgwZuNWMZHLXrkLdsk/S +pmVSMQZB584+2y/LrUATKo72Whx3yXxNxBXLL3/vqHef2luCDQMgxVHnTEnczppA +2ABhgqXCz5/RwYiOL1ZIXrHpvL/j1UmCyZBA695hBNOLtSDwBmVeFaMZpfsQqu/h +624mbu/ifSYyXDtrd4XFISqSIEd1eAzXSQNS90UWzxQJQ13Yv57PBPnCvbUxWiuI +UhkHfgldYLvoswZ8B6Eenfw4b2EguFEksaYtrdiJ1H4F2EHP5i/ecXMFfBZ6f5+G +SDzkODUUnniIW3j6XQKRERWp5t1bSYopSWLI259ntmGHTe6zzRjCf4D2POJxA6cy +NDBXd1xxMZnjhDXO8y4vg8MIp4WhvNMvBAOS85mb2cFbL3eYZDokFe0XhTjEklai +YdLSTGa0aU2x5u6FDGulvSwBSuHknHFfk9ABOZ0bVpMr7pYWQ0M/lB+OdU+msbkx +liMVyrnsZjTfYhv/X8HQUKy087i7D6FNtcmNENkJKN7AAjnDaPiBizdMVWNSKrEa +ox+V1uWpXba0HFtl57V1c3FvuXYtpQ7vSk3BrJMtSkkWalWmCxE6df7nJeygG4tj +CskvgR75N6TOiNK6C8gKXZbirufICH47hJHzPbfDK+Wwl+tccXqL+5rcqGpqaHGw +ED3Y5QkqLuk0wCBiyyuEX4E+l/Is64dRaiahjqp2M/SA4zuk1kIhEPRGyN9W6jx4 +T3yocMdXq7N87O+gA5XW+TPZlDxZeGIJK2V1qWpuvdbBFbT/PEdXVbFhErEVs2Vp +KIxWGYxeajOruPdHBAbLtJaWiqqG84QCglbd240eG7fXse+onVHa9r0mhIgBdzOa +Keheq5Wrjf6Zk9ljnzCEl79Bg92ZTWwOmOsXAWVL0eHS9VOkuSfjIVhEP0D69fxE +dOeMB5ECy5vlnejkRnM0jtfaRuM+7ZNydKAL0z3gKjnBzYotNu8qcw/Pf/+c5qy3 +M1kZGeY/y+wdj+vHC9Tmi8dk21+ZEuZJ8Yb0kvv3lSo5UHdv8OxxQyVQdXc8M/Nx +ZiMo8lryBhNbK6AIguukcTUo7HXXLg7+M78H7VKq/syZJvBVU9RT0NU7Z+amFgh+ +YXQPnu5IUCPAxTIEU0f17GXMLLkp2rRNMLiJVGZmyD+BTkWhc7z+z4jgtR16gqUA +GBBzYI3ZlOekfNEKIemLYn3AaPAB79myaJxVsy/9KzNj2xv3r2ClAF0EWJLnhMrq +dqHqhxkqjTeDAfqJwNrxWJ7Wvp5I9uJ+a2wIyrY5gluhEcdiaw3ehB9/ykLAMvtQ +lpntxcSG4bJTbVuDW/JNRQ== +-----END ENCRYPTED PRIVATE KEY----- + + +-----BEGIN CERTIFICATE----- +MIIDRTCCAi2gAwIBAgIRAIBVgErK4BCQMPt5R/MRR6kwDQYJKoZIhvcNAQELBQAw +DzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzUxNTZaFw0yNDAyMTEwNzUxNTZa +MA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDNujpXmw+b3VrDjKzwJC4gjjszbYayy4EAg0pPFkDNs+UdxX+Y50ug9W/zWxui +axZNXFv+RsNYjg4T+expaDf2feB+i5UPcbqJsV0OynybngdXwkvjQpbvXkPq/hHy +ODq0DOPiTCjiB7uaVmOYiJEV9SdNpdGIDElIJI9xjX0OSBvZlaR78rf2aJUMFC8Z +jazFzZWsQpOrbmAzQJD2gE6oS/AP1NbAXfKP3cBBK3iWEmA358XMuno23grw5cmQ +UT1mptG50rTTrcty+UZFM2VK4+mV7iM3krBrqJUCBgRrfkSpTjz9k1syTMNAJJ5S +FNGsqsWIS4h1URyWJsLXdcZ7AgMBAAGjgZswgZgwCQYDVR0TBAIwADAdBgNVHQ4E +FgQUjp4ln09TbR3WLAgDLGbDb5AWU5YwSgYDVR0jBEMwQYAUPwGq0LHvvgK4J9XB +8b16BvFvj3OhE6QRMA8xDTALBgNVBAMMBHRlc3SCFHKh49rkvHCvUmEIiZqCEf+3 +1ND6MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0B +AQsFAAOCAQEAAGfSk+Zpy/32n9/2WSAq9g8DurPaZd5iIzaijU8nIg49AYDZWc3F +8Bqbxej0b+LHKfw3IS9vnbiM9m43ybFKDZ3lzQpLAQqYj0bpJJec73XdpPczfd8J +80y2PDinLiYdaPmHmq5sYNneMvFpZpfLIIENtQF0tXOMhStac+rN5SUTRDokCgpy +TULMC1rJlgUgN/sblRiNZv8Q+DrYA2xsN27eUVkIftEzEQh07fw/TRkAgoiflWam +6fFzVeF6P66m4bdR35IoGUIdpaftueQA6qdV41USRV/54aUfE/HuGjHlrp0u793Y +VrJ89roIQdsTFjEOXUGybZgB5EOi0TSekQ== +-----END CERTIFICATE----- + + +-----BEGIN CERTIFICATE----- +MIIDNjCCAh6gAwIBAgIUcqHj2uS8cK9SYQiJmoIR/7fU0PowDQYJKoZIhvcNAQEL +BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzQ5MTBaFw0zMTExMDYwNzQ5 +MTBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCibt8kh9lqTC0O631rPHN0kMQ4kMQ/eZ59mKhAJZ3rBchIBrQne2yTw2z+ +X1ESa3VTkW2jyJ5r7iuo+Xyc8246tfBwO3u0DJ2DeZZOYPzMg48nJNxs3ur3iXAT +r6Aiwp0gtMNC2XcW7y5OPl8l+BhSt2PsWcdEdmLJgvRPJ2x+Ea8wivuw6FO6byK7 +Mxw7/CbNMw8Eey9eSz9kWDrgetS0kOgfqtt1ZnKDZkbLy8jFl0xW488VUrefUR1g +lOje8QySjDvzT8sUR0lASyS+/J6j/3gLlSS42e4SxMz00jEus+ye56cO16Pc+vKI +Xsev8cRPiSDTZTvc7Eaq/OcKVl11AgMBAAGjgYkwgYYwHQYDVR0OBBYEFD8BqtCx +774CuCfVwfG9egbxb49zMEoGA1UdIwRDMEGAFD8BqtCx774CuCfVwfG9egbxb49z +oROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCImaghH/t9TQ+jAMBgNV +HRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAXUrDhVAX +TkbhKRBuhUGQb03RyACQKBFM/SwhrmwpQMXo7BUuqWJ27U5/TRHrfKJxDgppmwIs +qmtrT07tA7e/OyFSZtZ9p/4H+5xM9FCsmu6YMQ3ZloHHGWmibrDNK70frVgRAEAS +FyAsEgpKZCr6OJNd7v2dbvO4AniZVVvccU17cJAx177YC3fNIuRtpHkm93D3qI+1 +4SED7rktVfXUKs6RMFmqIum5WRzgiJBAtk2GVQMrAAu/xmUPS/aqzstNte4KQ+UY +2qI9v1wYM8j+BT5nsBT02K+zOsYdkG39n7QEfcecPAjOkKsaFbSf/WZcsb6oCVgl +d/Nz24kfh76SqQ== +-----END CERTIFICATE----- + +key-direction 1 + +# +# 2048 bit OpenVPN static key +# +-----BEGIN OpenVPN Static key V1----- +456226c4d5a6895c48dad7fd5d36ee57 +3eb280683fbbfe1699d63e9fd4e5ec5b +70500489c3ec36e0c30d6b18f9c48b6b +aede839a99d492fd26beb51317c08eb2 +ebb320a0b980da0b13a88e37559594f5 +03b21fa6d72548f7be5fdb41ad1de315 +82373a95c5c503c1101236f43a59ec68 +ddc9a83d4b4a4437f2db9e16bcbd433a +5211d060bc8376f1efe99bbf2413e543 +4e4473d5028c95f33ad5df3637505c31 +bb7661b03e7d882c3ec1c5ca5f9c2277 +09e2e4323392efb0dff0abadbe6d6887 +27bddf4a2f7f795fe7c227813f76cac2 +9e919074c638ad36e5001a187d113c4b +3faab93dde06734c15a198ad686a315a +3e1f91612528d4f6c4281916625e54b1 +-----END OpenVPN Static key V1----- + + diff --git a/tests/playwright/rte/oss-cluster-7-rs/Dockerfile b/tests/playwright/rte/oss-cluster-7-rs/Dockerfile new file mode 100644 index 0000000000..b3f1a97011 --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7-rs/Dockerfile @@ -0,0 +1,19 @@ +FROM redislabs/rejson:1.0.8 as rejson + +FROM redis:7.0.8 + +COPY redis.conf /etc/redis/ +COPY --from=rejson /usr/lib/redis/modules/rejson.so /etc/redis/modules/ + +CMD ls -la +RUN ls -la /etc/redis/modules + +ADD https://s3.amazonaws.com/redisinsight.test/public/rte/modules/redisearch-coord/redisearch-coord.so.tar.gz . + +RUN ls -la +RUN ls -la /etc/redis/modules +RUN tar -xvzf redisearch-coord.so.tar.gz && rm redisearch-coord.so.tar.gz && cp redisearch-coord.so /etc/redis/modules +RUN ls -la +RUN ls -la /etc/redis/modules + +CMD [ "redis-server", "/etc/redis/redis.conf" ] diff --git a/tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh b/tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh new file mode 100644 index 0000000000..a21401b4df --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +echo 'Try to sleep for a while...' +sleep 25 +echo 'Creating cluster...' +echo "yes" | redis-cli \ + --cluster create \ + 172.31.100.221:6379 \ + 172.31.100.222:6379 \ + 172.31.100.223:6379 \ + --cluster-replicas 0 \ + && redis-server diff --git a/tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile b/tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile new file mode 100644 index 0000000000..24762ba80e --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile @@ -0,0 +1,9 @@ +FROM redis:7.0.6 + +USER root + +COPY cluster-rs-create.sh ./ + +RUN chmod a+x cluster-rs-create.sh + +CMD ["/bin/sh", "./cluster-rs-create.sh"] diff --git a/tests/playwright/rte/oss-cluster-7-rs/redis.conf b/tests/playwright/rte/oss-cluster-7-rs/redis.conf new file mode 100644 index 0000000000..1c7ab660d5 --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7-rs/redis.conf @@ -0,0 +1,1881 @@ +# Redis configuration file example. +# +# Note that in order to read the configuration file, Redis must be +# started with the file path as first argument: +# +# ./redis-server /path/to/redis.conf + +# Note on units: when memory size is needed, it is possible to specify +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis servers but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Note that option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# include /path/to/local.conf +# include /path/to/other.conf + +################################## MODULES ##################################### + +# Load modules at startup. If the server is not able to load modules +# it will abort. It is possible to use multiple loadmodule directives. +# +loadmodule /etc/redis/modules/rejson.so +loadmodule /etc/redis/modules/redisearch-coord.so +# loadmodule /path/to/other_module.so + +################################## NETWORK ##################################### + +# By default, if no "bind" configuration directive is specified, Redis listens +# for connections from all available network interfaces on the host machine. +# It is possible to listen to just one or multiple selected interfaces using +# the "bind" configuration directive, followed by one or more IP addresses. +# +# Examples: +# +# bind 192.168.1.100 10.0.0.1 +# bind 127.0.0.1 ::1 +# +# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the +# internet, binding to all the interfaces is dangerous and will expose the +# instance to everybody on the internet. So by default we uncomment the +# following bind directive, that will force Redis to listen only on the +# IPv4 loopback interface address (this means Redis will only be able to +# accept client connections from the same host that it is running on). +# +# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES +# JUST COMMENT OUT THE FOLLOWING LINE. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# bind 127.0.0.1 + +# Protected mode is a layer of security protection, in order to avoid that +# Redis instances left open on the internet are accessed and exploited. +# +# When protected mode is on and if: +# +# 1) The server is not binding explicitly to a set of addresses using the +# "bind" directive. +# 2) No password is configured. +# +# The server only accepts connections from clients connecting from the +# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain +# sockets. +# +# By default protected mode is enabled. You should disable it only if +# you are sure you want clients from other hosts to connect to Redis +# even if no authentication is configured, nor a specific set of interfaces +# are explicitly listed using the "bind" directive. +# protected-mode yes + +# Accept connections on the specified port, default is 6379 (IANA #815344). +# If port 0 is specified Redis will not listen on a TCP socket. +port 6379 + +# TCP listen() backlog. +# +# In high requests-per-second environments you need a high backlog in order +# to avoid slow clients connection issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# Unix socket. +# +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +# unixsocket /tmp/redis.sock +# unixsocketperm 700 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# +# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence +# of communication. This is useful for two reasons: +# +# 1) Detect dead peers. +# 2) Force network equipment in the middle to consider the connection to be +# alive. +# +# On Linux, the specified value (in seconds) is the period used to send ACKs. +# Note that to close the connection the double of the time is needed. +# On other kernels the period depends on the kernel configuration. +# +# A reasonable value for this option is 300 seconds, which is the new +# Redis default starting with Redis 3.2.1. +tcp-keepalive 300 + +################################# TLS/SSL ##################################### + +# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration +# directive can be used to define TLS-listening ports. To enable TLS on the +# default port, use: +# +# port 0 +# tls-port 6379 + +# Configure a X.509 certificate and private key to use for authenticating the +# server to connected clients, masters or cluster peers. These files should be +# PEM formatted. +# +# tls-cert-file /etc/redis/redis.crt +# tls-key-file /etc/redis/redis.key + +# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: +# +# tls-dh-params-file redis.dh + +# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL +# clients and peers. Redis requires an explicit configuration of at least one +# of these, and will not implicitly use the system wide configuration. +# +# tls-ca-cert-file /etc/redis/ca.crt +# tls-ca-cert-dir /etc/ssl/certs + +# By default, clients (including replica servers) on a TLS port are required +# to authenticate using valid client side certificates. +# +# If "no" is specified, client certificates are not required and not accepted. +# If "optional" is specified, client certificates are accepted and must be +# valid if provided, but are not required. +# +# tls-auth-clients yes +# tls-auth-clients optional + +# By default, a Redis replica does not attempt to establish a TLS connection +# with its master. +# +# Use the following directive to enable TLS on replication links. +# +# tls-replication yes + +# By default, the Redis Cluster bus uses a plain TCP connection. To enable +# TLS for the bus protocol, use the following directive: +# +# tls-cluster yes + +# Explicitly specify TLS versions to support. Allowed values are case insensitive +# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or +# any combination. To enable only TLSv1.2 and TLSv1.3, use: +# +# tls-protocols "TLSv1.2 TLSv1.3" + +# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information +# about the syntax of this string. +# +# Note: this configuration applies only to <= TLSv1.2. +# +# tls-ciphers DEFAULT:!MEDIUM + +# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more +# information about the syntax of this string, and specifically for TLSv1.3 +# ciphersuites. +# +# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 + +# When choosing a cipher, use the server's preference instead of the client +# preference. By default, the server follows the client's preference. +# +# tls-prefer-server-ciphers yes + +# By default, TLS session caching is enabled to allow faster and less expensive +# reconnections by clients that support it. Use the following directive to disable +# caching. +# +# tls-session-caching no + +# Change the default number of TLS sessions cached. A zero value sets the cache +# to unlimited size. The default size is 20480. +# +# tls-session-cache-size 5000 + +# Change the default timeout of cached TLS sessions. The default timeout is 300 +# seconds. +# +# tls-session-cache-timeout 60 + +################################# GENERAL ##################################### + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize no + +# If you run Redis from upstart or systemd, Redis can interact with your +# supervision tree. Options: +# supervised no - no supervision interaction +# supervised upstart - signal upstart by putting Redis into SIGSTOP mode +# requires "expect stop" in your upstart job config +# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET +# supervised auto - detect upstart or systemd method based on +# UPSTART_JOB or NOTIFY_SOCKET environment variables +# Note: these supervision methods only signal "process is ready." +# They do not enable continuous pings back to your supervisor. +supervised no + +# If a pid file is specified, Redis writes it where specified at startup +# and removes it at exit. +# +# When the server runs non daemonized, no pid file is created if none is +# specified in the configuration. When the server is daemonized, the pid file +# is used even if not specified, defaulting to "/var/run/redis.pid". +# +# Creating a pid file is best effort: if Redis is not able to create it +# nothing bad happens, the server will start and run normally. +pidfile /var/run/redis_6379.pid + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel notice + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile "" + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +# By default Redis shows an ASCII art logo only when started to log to the +# standard output and if the standard output is a TTY. Basically this means +# that normally a logo is displayed only in interactive sessions. +# +# However it is possible to force the pre-4.0 behavior and always show a +# ASCII art logo in startup logs by setting the following option to yes. +always-show-logo yes + +################################ SNAPSHOTTING ################################ +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behavior will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving completely by commenting out all "save" lines. +# +# It is also possible to remove all the previously configured save +# points by adding a save directive with a single empty string argument +# like in the following example: +# +# save "" + +save 900 1 +save 300 10 +save 60 10000 + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# By default compression is enabled as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + +# The filename where to dump the DB +dbfilename dump.rdb + +# Remove RDB files used by replication in instances without persistence +# enabled. By default this option is disabled, however there are environments +# where for regulations or other security concerns, RDB files persisted on +# disk by masters in order to feed replicas, or stored on disk by replicas +# in order to load them for the initial synchronization, should be deleted +# ASAP. Note that this option ONLY WORKS in instances that have both AOF +# and RDB persistence disabled, otherwise is completely ignored. +# +# An alternative (and sometimes better) way to obtain the same effect is +# to use diskless replication on both master and replicas instances. However +# in the case of replicas, diskless is not always an option. +# in the case of replicas, diskless is not always an option. +rdb-del-sync-files no + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Replica replication. Use replicaof to make a Redis instance a copy of +# another Redis server. A few things to understand ASAP about Redis replication. +# +# +------------------+ +---------------+ +# | Master | ---> | Replica | +# | (receive writes) | | (exact copy) | +# +------------------+ +---------------+ +# +# 1) Redis replication is asynchronous, but you can configure a master to +# stop accepting writes if it appears to be not connected with at least +# a given number of replicas. +# 2) Redis replicas are able to perform a partial resynchronization with the +# master if the replication link is lost for a relatively small amount of +# time. You may want to configure the replication backlog size (see the next +# sections of this file) with a sensible value depending on your needs. +# 3) Replication is automatic and does not need user intervention. After a +# network partition replicas automatically try to reconnect to masters +# and resynchronize with them. +# +# replicaof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the replica to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the replica request. +# +# masterauth defaultpass +# +# However this is not enough if you are using Redis ACLs (for Redis version +# 6 or greater), and the default user is not capable of running the PSYNC +# command and/or other commands needed for replication. In this case it's +# better to configure a special user to use with replication, and specify the +# masteruser configuration as such: +# +# masteruser +# +# When masteruser is specified, the replica will authenticate against its +# master using the new AUTH form: AUTH . + +# When a replica loses its connection with the master, or when the replication +# is still in progress, the replica can act in two different ways: +# +# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) If replica-serve-stale-data is set to 'no' the replica will reply with +# an error "SYNC with master in progress" to all commands except: +# INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, +# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, +# HOST and LATENCY. +# +replica-serve-stale-data yes + +# You can configure a replica instance to accept writes or not. Writing against +# a replica instance may be useful to store some ephemeral data (because data +# written on a replica will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default replicas are read-only. +# +# Note: read only replicas are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only replica exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only replicas using 'rename-command' to shadow all the +# administrative / dangerous commands. +replica-read-only yes + +# Replication SYNC strategy: disk or socket. +# +# New replicas and reconnecting replicas that are not able to continue the +# replication process just receiving differences, need to do what is called a +# "full synchronization". An RDB file is transmitted from the master to the +# replicas. +# +# The transmission can happen in two different ways: +# +# 1) Disk-backed: The Redis master creates a new process that writes the RDB +# file on disk. Later the file is transferred by the parent +# process to the replicas incrementally. +# 2) Diskless: The Redis master creates a new process that directly writes the +# RDB file to replica sockets, without touching the disk at all. +# +# With disk-backed replication, while the RDB file is generated, more replicas +# can be queued and served with the RDB file as soon as the current child +# producing the RDB file finishes its work. With diskless replication instead +# once the transfer starts, new replicas arriving will be queued and a new +# transfer will start when the current one terminates. +# +# When diskless replication is used, the master waits a configurable amount of +# time (in seconds) before starting the transfer in the hope that multiple +# replicas will arrive and the transfer can be parallelized. +# +# With slow disks and fast (large bandwidth) networks, diskless replication +# works better. +repl-diskless-sync no + +# When diskless replication is enabled, it is possible to configure the delay +# the server waits in order to spawn the child that transfers the RDB via socket +# to the replicas. +# +# This is important since once the transfer starts, it is not possible to serve +# new replicas arriving, that will be queued for the next RDB transfer, so the +# server waits a delay in order to let more replicas arrive. +# +# The delay is specified in seconds, and by default is 5 seconds. To disable +# it entirely just set it to 0 seconds and the transfer will start ASAP. +repl-diskless-sync-delay 5 + +# ----------------------------------------------------------------------------- +# WARNING: RDB diskless load is experimental. Since in this setup the replica +# does not immediately store an RDB on disk, it may cause data loss during +# failovers. RDB diskless load + Redis modules not handling I/O reads may also +# cause Redis to abort in case of I/O errors during the initial synchronization +# stage with the master. Use only if your do what you are doing. +# ----------------------------------------------------------------------------- +# +# Replica can load the RDB it reads from the replication link directly from the +# socket, or store the RDB to a file and read that file after it was completely +# received from the master. +# +# In many cases the disk is slower than the network, and storing and loading +# the RDB file may increase replication time (and even increase the master's +# Copy on Write memory and salve buffers). +# However, parsing the RDB file directly from the socket may mean that we have +# to flush the contents of the current database before the full rdb was +# received. For this reason we have the following options: +# +# "disabled" - Don't use diskless load (store the rdb file to the disk first) +# "on-empty-db" - Use diskless load only when it is completely safe. +# "swapdb" - Keep a copy of the current db contents in RAM while parsing +# the data directly from the socket. note that this requires +# sufficient memory, if you don't have it, you risk an OOM kill. +repl-diskless-load disabled + +# Replicas send PINGs to server in a predefined interval. It's possible to +# change this interval with the repl_ping_replica_period option. The default +# value is 10 seconds. +# +# repl-ping-replica-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of replica. +# 2) Master timeout from the point of view of replicas (data, pings). +# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-replica-period otherwise a timeout will be detected +# every time there is low traffic between the master and the replica. The default +# value is 60 seconds. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the replica socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to replicas. But this can add a delay for +# the data to appear on the replica side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the replica side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and replicas are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# Set the replication backlog size. The backlog is a buffer that accumulates +# replica data when replicas are disconnected for some time, so that when a +# replica wants to reconnect again, often a full resync is not needed, but a +# partial resync is enough, just passing the portion of data the replica +# missed while disconnected. +# +# The bigger the replication backlog, the longer the replica can endure the +# disconnect and later be able to perform a partial resynchronization. +# +# The backlog is only allocated if there is at least one replica connected. +# +# repl-backlog-size 1mb + +# After a master has no connected replicas for some time, the backlog will be +# freed. The following option configures the amount of seconds that need to +# elapse, starting from the time the last replica disconnected, for the backlog +# buffer to be freed. +# +# Note that replicas never free the backlog for timeout, since they may be +# promoted to masters later, and should be able to correctly "partially +# resynchronize" with other replicas: hence they should always accumulate backlog. +# +# A value of 0 means to never release the backlog. +# +# repl-backlog-ttl 3600 + +# The replica priority is an integer number published by Redis in the INFO +# output. It is used by Redis Sentinel in order to select a replica to promote +# into a master if the master is no longer working correctly. +# +# A replica with a low priority number is considered better for promotion, so +# for instance if there are three replicas with priority 10, 100, 25 Sentinel +# will pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the replica as not able to perform the +# role of master, so a replica with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +replica-priority 100 + +# It is possible for a master to stop accepting writes if there are less than +# N replicas connected, having a lag less or equal than M seconds. +# +# The N replicas need to be in "online" state. +# +# The lag in seconds, that must be <= the specified value, is calculated from +# the last ping received from the replica, that is usually sent every second. +# +# This option does not GUARANTEE that N replicas will accept the write, but +# will limit the window of exposure for lost writes in case not enough replicas +# are available, to the specified number of seconds. +# +# For example to require at least 3 replicas with a lag <= 10 seconds use: +# +# min-replicas-to-write 3 +# min-replicas-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-replicas-to-write is set to 0 (feature disabled) and +# min-replicas-max-lag is set to 10. + +# A Redis master is able to list the address and port of the attached +# replicas in different ways. For example the "INFO replication" section +# offers this information, which is used, among other tools, by +# Redis Sentinel in order to discover replica instances. +# Another place where this info is available is in the output of the +# "ROLE" command of a master. +# +# The listed IP address and port normally reported by a replica is +# obtained in the following way: +# +# IP: The address is auto detected by checking the peer address +# of the socket used by the replica to connect with the master. +# +# Port: The port is communicated by the replica during the replication +# handshake, and is normally the port that the replica is using to +# listen for connections. +# +# However when port forwarding or Network Address Translation (NAT) is +# used, the replica may actually be reachable via different IP and port +# pairs. The following two options can be used by a replica in order to +# report to its master a specific set of IP and port, so that both INFO +# and ROLE will report those values. +# +# There is no need to use both the options if you need to override just +# the port or the IP address. +# +# replica-announce-ip 5.5.5.5 +# replica-announce-port 1234 + +############################### KEYS TRACKING ################################# + +# Redis implements server assisted support for client side caching of values. +# This is implemented using an invalidation table that remembers, using +# 16 millions of slots, what clients may have certain subsets of keys. In turn +# this is used in order to send invalidation messages to clients. Please +# check this page to understand more about the feature: +# +# https://redis.io/topics/client-side-caching +# +# When tracking is enabled for a client, all the read only queries are assumed +# to be cached: this will force Redis to store information in the invalidation +# table. When keys are modified, such information is flushed away, and +# invalidation messages are sent to the clients. However if the workload is +# heavily dominated by reads, Redis could use more and more memory in order +# to track the keys fetched by many clients. +# +# For this reason it is possible to configure a maximum fill value for the +# invalidation table. By default it is set to 1M of keys, and once this limit +# is reached, Redis will start to evict keys in the invalidation table +# even if they were not modified, just to reclaim memory: this will in turn +# force the clients to invalidate the cached values. Basically the table +# maximum size is a trade off between the memory you want to spend server +# side to track information about who cached what, and the ability of clients +# to retain cached objects in memory. +# +# If you set the value to 0, it means there are no limits, and Redis will +# retain as many keys as needed in the invalidation table. +# In the "stats" INFO section, you can find information about the number of +# keys in the invalidation table at every given moment. +# +# Note: when key tracking is used in broadcasting mode, no memory is used +# in the server side so this setting is useless. +# +# tracking-table-max-keys 1000000 + +################################## SECURITY ################################### + +# Warning: since Redis is pretty fast, an outside user can try up to +# 1 million passwords per second against a modern box. This means that you +# should use very strong passwords, otherwise they will be very easy to break. +# Note that because the password is really a shared secret between the client +# and the server, and should not be memorized by any human, the password +# can be easily a long string from /dev/urandom or whatever, so by using a +# long and unguessable password no brute force attack will be possible. + +# Redis ACL users are defined in the following format: +# +# user ... acl rules ... +# +# For example: +# +# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 +# +# The special username "default" is used for new connections. If this user +# has the "nopass" rule, then new connections will be immediately authenticated +# as the "default" user without the need of any password provided via the +# AUTH command. Otherwise if the "default" user is not flagged with "nopass" +# the connections will start in not authenticated state, and will require +# AUTH (or the HELLO command AUTH option) in order to be authenticated and +# start to work. +# +# The ACL rules that describe what a user can do are the following: +# +# on Enable the user: it is possible to authenticate as this user. +# off Disable the user: it's no longer possible to authenticate +# with this user, however the already authenticated connections +# will still work. +# + Allow the execution of that command +# - Disallow the execution of that command +# +@ Allow the execution of all the commands in such category +# with valid categories are like @admin, @set, @sortedset, ... +# and so forth, see the full list in the server.c file where +# the Redis command table is described and defined. +# The special category @all means all the commands, but currently +# present in the server, and that will be loaded in the future +# via modules. +# +|subcommand Allow a specific subcommand of an otherwise +# disabled command. Note that this form is not +# allowed as negative like -DEBUG|SEGFAULT, but +# only additive starting with "+". +# allcommands Alias for +@all. Note that it implies the ability to execute +# all the future commands loaded via the modules system. +# nocommands Alias for -@all. +# ~ Add a pattern of keys that can be mentioned as part of +# commands. For instance ~* allows all the keys. The pattern +# is a glob-style pattern like the one of KEYS. +# It is possible to specify multiple patterns. +# allkeys Alias for ~* +# resetkeys Flush the list of allowed keys patterns. +# > Add this password to the list of valid password for the user. +# For example >mypass will add "mypass" to the list. +# This directive clears the "nopass" flag (see later). +# < Remove this password from the list of valid passwords. +# nopass All the set passwords of the user are removed, and the user +# is flagged as requiring no password: it means that every +# password will work against this user. If this directive is +# used for the default user, every new connection will be +# immediately authenticated with the default user without +# any explicit AUTH command required. Note that the "resetpass" +# directive will clear this condition. +# resetpass Flush the list of allowed passwords. Moreover removes the +# "nopass" status. After "resetpass" the user has no associated +# passwords and there is no way to authenticate without adding +# some password (or setting it as "nopass" later). +# reset Performs the following actions: resetpass, resetkeys, off, +# -@all. The user returns to the same state it has immediately +# after its creation. +# +# ACL rules can be specified in any order: for instance you can start with +# passwords, then flags, or key patterns. However note that the additive +# and subtractive rules will CHANGE MEANING depending on the ordering. +# For instance see the following example: +# +# user alice on +@all -DEBUG ~* >somepassword +# +# This will allow "alice" to use all the commands with the exception of the +# DEBUG command, since +@all added all the commands to the set of the commands +# alice can use, and later DEBUG was removed. However if we invert the order +# of two ACL rules the result will be different: +# +# user alice on -DEBUG +@all ~* >somepassword +# +# Now DEBUG was removed when alice had yet no commands in the set of allowed +# commands, later all the commands are added, so the user will be able to +# execute everything. +# +# Basically ACL rules are processed left-to-right. +# +# For more information about ACL configuration please refer to +# the Redis web site at https://redis.io/topics/acl + +# ACL LOG +# +# The ACL Log tracks failed commands and authentication events associated +# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked +# by ACLs. The ACL Log is stored in memory. You can reclaim memory with +# ACL LOG RESET. Define the maximum entry length of the ACL Log below. +acllog-max-len 128 + +# Using an external ACL file +# +# Instead of configuring users here in this file, it is possible to use +# a stand-alone file just listing users. The two methods cannot be mixed: +# if you configure users here and at the same time you activate the external +# ACL file, the server will refuse to start. +# +# The format of the external ACL user file is exactly the same as the +# format that is used inside redis.conf to describe users. +# +# aclfile /etc/redis/users.acl + +# aclfile /etc/redis/users.acl + +# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility +# layer on top of the new ACL system. The option effect will be just setting +# the password for the default user. Clients will still authenticate using +# AUTH as usually, or more explicitly with AUTH default +# if they follow the new protocol: both will work. +# +# requirepass somepass + +# Command renaming (DEPRECATED). +# +# ------------------------------------------------------------------------ +# WARNING: avoid using this option if possible. Instead use ACLs to remove +# commands from the default user, and put them only in some admin user you +# create for administrative purposes. +# ------------------------------------------------------------------------ +# +# It is possible to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# hard to guess so that it will still be available for internal-use tools +# but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possible to completely kill a command by renaming it into +# an empty string: +# +# rename-command CONFIG "" +# +# Please note that changing the name of commands that are logged into the +# AOF file or transmitted to replicas may cause problems. + +################################### CLIENTS #################################### + +# Set the max number of connected clients at the same time. By default +# this limit is set to 10000 clients, however if the Redis server is not +# able to configure the process file limit to allow for the specified limit +# the max number of allowed clients is set to the current file limit +# minus 32 (as Redis reserves a few file descriptors for internal uses). +# +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# IMPORTANT: When Redis Cluster is used, the max number of connections is also +# shared with the cluster bus: every node in the cluster will use two +# connections, one incoming and another outgoing. It is important to size the +# limit accordingly in case of very large clusters. +# +# maxclients 10000 + +############################## MEMORY MANAGEMENT ################################ + +# Set a memory usage limit to the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# according to the eviction policy selected (see maxmemory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU or LFU cache, or to +# set a hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have replicas attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the replicas are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of replicas is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have replicas attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for replica +# output buffers (but this is not needed if the policy is 'noeviction'). +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached. You can select one from the following behaviors: +# +# volatile-lru -> Evict using approximated LRU, only keys with an expire set. +# allkeys-lru -> Evict any key using approximated LRU. +# volatile-lfu -> Evict using approximated LFU, only keys with an expire set. +# allkeys-lfu -> Evict any key using approximated LFU. +# volatile-random -> Remove a random key having an expire set. +# allkeys-random -> Remove a random key, any key. +# volatile-ttl -> Remove the key with the nearest expire time (minor TTL) +# noeviction -> Don't evict anything, just return an error on write operations. +# +# LRU means Least Recently Used +# LFU means Least Frequently Used +# +# Both LRU, LFU and volatile-ttl are implemented using approximated +# randomized algorithms. +# +# Note: with any of the above policies, Redis will return an error on write +# operations, when there are no suitable keys for eviction. +# +# At the date of writing these commands are: set setnx setex append +# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd +# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby +# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby +# getset mset msetnx exec sort +# +# The default is: +# +# maxmemory-policy noeviction + +# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can tune it for speed or +# accuracy. By default Redis will check five keys and pick the one that was +# used least recently, you can change the sample size using the following +# configuration directive. +# +# The default of 5 produces good enough results. 10 Approximates very closely +# true LRU but costs more CPU. 3 is faster but not very accurate. +# +# maxmemory-samples 5 + +# Starting from Redis 5, by default a replica will ignore its maxmemory setting +# (unless it is promoted to master after a failover or manually). It means +# that the eviction of keys will be just handled by the master, sending the +# DEL commands to the replica as keys evict in the master side. +# +# This behavior ensures that masters and replicas stay consistent, and is usually +# what you want, however if your replica is writable, or you want the replica +# to have a different memory setting, and you are sure all the writes performed +# to the replica are idempotent, then you may change this default (but be sure +# to understand what you are doing). +# +# Note that since the replica by default does not evict, it may end using more +# memory than the one set via maxmemory (there are certain buffers that may +# be larger on the replica, or data structures may sometimes take more memory +# and so forth). So make sure you monitor your replicas and make sure they +# have enough memory to never hit a real out-of-memory condition before the +# master hits the configured maxmemory setting. +# +# replica-ignore-maxmemory yes + +# Redis reclaims expired keys in two ways: upon access when those keys are +# found to be expired, and also in background, in what is called the +# "active expire key". The key space is slowly and interactively scanned +# looking for expired keys to reclaim, so that it is possible to free memory +# of keys that are expired and will never be accessed again in a short time. +# +# The default effort of the expire cycle will try to avoid having more than +# ten percent of expired keys still in memory, and will try to avoid consuming +# more than 25% of total memory and to add latency to the system. However +# it is possible to increase the expire "effort" that is normally set to +# "1", to a greater value, up to the value "10". At its maximum value the +# system will use more CPU, longer cycles (and technically may introduce +# more latency), and will tolerate less already expired keys still present +# in the system. It's a tradeoff between memory, CPU and latency. +# +# active-expire-effort 1 + +############################# LAZY FREEING #################################### + +# Redis has two primitives to delete keys. One is called DEL and is a blocking +# deletion of the object. It means that the server stops processing new commands +# in order to reclaim all the memory associated with an object in a synchronous +# way. If the key deleted is associated with a small object, the time needed +# in order to execute the DEL command is very small and comparable to most other +# O(1) or O(log_N) commands in Redis. However if the key is associated with an +# aggregated value containing millions of elements, the server can block for +# a long time (even seconds) in order to complete the operation. +# +# For the above reasons Redis also offers non blocking deletion primitives +# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and +# FLUSHDB commands, in order to reclaim memory in background. Those commands +# are executed in constant time. Another thread will incrementally free the +# object in the background as fast as possible. +# +# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. +# It's up to the design of the application to understand when it is a good +# idea to use one or the other. However the Redis server sometimes has to +# delete keys or flush the whole database as a side effect of other operations. +# Specifically Redis deletes objects independently of a user call in the +# following scenarios: +# +# 1) On eviction, because of the maxmemory and maxmemory policy configurations, +# in order to make room for new data, without going over the specified +# memory limit. +# 2) Because of expire: when a key with an associated time to live (see the +# EXPIRE command) must be deleted from memory. +# 3) Because of a side effect of a command that stores data on a key that may +# already exist. For example the RENAME command may delete the old key +# content when it is replaced with another one. Similarly SUNIONSTORE +# or SORT with STORE option may delete existing keys. The SET command +# itself removes any old content of the specified key in order to replace +# it with the specified string. +# 4) During replication, when a replica performs a full resynchronization with +# its master, the content of the whole database is removed in order to +# load the RDB file just transferred. +# +# In all the above cases the default is to delete objects in a blocking way, +# like if DEL was called. However you can configure each case specifically +# in order to instead release memory in a non-blocking way like if UNLINK +# was called, using the following configuration directives. + +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no + +# It is also possible, for the case when to replace the user code DEL calls +# with UNLINK calls is not easy, to modify the default behavior of the DEL +# command to act exactly like UNLINK, using the following configuration +# directive: + +lazyfree-lazy-user-del no + +################################ THREADED I/O ################################# + +# Redis is mostly single threaded, however there are certain threaded +# operations such as UNLINK, slow I/O accesses and other things that are +# performed on side threads. +# +# Now it is also possible to handle Redis clients socket reads and writes +# in different I/O threads. Since especially writing is so slow, normally +# Redis users use pipelining in order to speed up the Redis performances per +# core, and spawn multiple instances in order to scale more. Using I/O +# threads it is possible to easily speedup two times Redis without resorting +# to pipelining nor sharding of the instance. +# +# By default threading is disabled, we suggest enabling it only in machines +# that have at least 4 or more cores, leaving at least one spare core. +# Using more than 8 threads is unlikely to help much. We also recommend using +# threaded I/O only if you actually have performance problems, with Redis +# instances being able to use a quite big percentage of CPU time, otherwise +# there is no point in using this feature. +# +# So for instance if you have a four cores boxes, try to use 2 or 3 I/O +# threads, if you have a 8 cores, try to use 6 threads. In order to +# enable I/O threads use the following configuration directive: +# +# io-threads 4 +# +# Setting io-threads to 1 will just use the main thread as usual. +# When I/O threads are enabled, we only use threads for writes, that is +# to thread the write(2) syscall and transfer the client buffers to the +# socket. However it is also possible to enable threading of reads and +# protocol parsing using the following configuration directive, by setting +# it to yes: +# +# io-threads-do-reads no +# +# Usually threading reads doesn't help much. +# +# NOTE 1: This configuration directive cannot be changed at runtime via +# CONFIG SET. Aso this feature currently does not work when SSL is +# enabled. +# +# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make +# sure you also run the benchmark itself in threaded mode, using the +# --threads option to match the number of Redis threads, otherwise you'll not +# be able to notice the improvements. + +############################ KERNEL OOM CONTROL ############################## + +# On Linux, it is possible to hint the kernel OOM killer on what processes +# should be killed first when out of memory. +# +# Enabling this feature makes Redis actively control the oom_score_adj value +# for all its processes, depending on their role. The default scores will +# attempt to have background child processes killed before all others, and +# replicas killed before masters. +# +# Redis supports three options: +# +# no: Don't make changes to oom-score-adj (default). +# yes: Alias to "relative" see below. +# absolute: Values in oom-score-adj-values are written as is to the kernel. +# relative: Values are used relative to the initial value of oom_score_adj when +# the server starts and are then clamped to a range of -1000 to 1000. +# Because typically the initial value is 0, they will often match the +# absolute values. +oom-score-adj no + +# When oom-score-adj is used, this directive controls the specific values used +# for master, replica and background child processes. Values range -2000 to +# 2000 (higher means more likely to be killed). +# +# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) +# can freely increase their value, but not decrease it below its initial +# settings. This means that setting oom-score-adj to "relative" and setting the +# oom-score-adj-values to positive values will always succeed. +oom-score-adj-values 0 200 800 + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check http://redis.io/topics/persistence for more information. + +appendonly no + +# The name of the append only file (default: "appendonly.aof") + +appendfilename "appendonly.aof" + +# The fsync() call tells the Operating System to actually write data on disk +# instead of waiting for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log. Slow, Safest. +# everysec: fsync only one time every second. Compromise. +# +# The default is "everysec", as that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving, the durability of Redis is +# the same as "appendfsync none". In practical terms, this means that it is +# possible to lose up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. + +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size grows by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (if no rewrite has happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a percentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +# An AOF file may be found to be truncated at the end during the Redis +# startup process, when the AOF data gets loaded back into memory. +# This may happen when the system where Redis is running +# crashes, especially when an ext4 filesystem is mounted without the +# data=ordered option (however this can't happen when Redis itself +# crashes or aborts but the operating system still works correctly). +# +# Redis can either exit with an error when this happens, or load as much +# data as possible (the default now) and start if the AOF file is found +# to be truncated at the end. The following option controls this behavior. +# +# If aof-load-truncated is set to yes, a truncated AOF file is loaded and +# the Redis server starts emitting a log to inform the user of the event. +# Otherwise if the option is set to no, the server aborts with an error +# and refuses to start. When the option is set to no, the user requires +# to fix the AOF file using the "redis-check-aof" utility before to restart +# the server. +# +# Note that if the AOF file will be found to be corrupted in the middle +# the server will still exit with an error. This option only applies when +# Redis will try to read more data from the AOF file but not enough bytes +# will be found. +aof-load-truncated yes + +# When rewriting the AOF file, Redis is able to use an RDB preamble in the +# AOF file for faster rewrites and recoveries. When this option is turned +# on the rewritten AOF file is composed of two different stanzas: +# +# [RDB file][AOF tail] +# +# When loading, Redis recognizes that the AOF file starts with the "REDIS" +# string and loads the prefixed RDB file, then continues loading the AOF +# tail. +aof-use-rdb-preamble yes + +################################ LUA SCRIPTING ############################### + +# Max execution time of a Lua script in milliseconds. +# +# If the maximum execution time is reached Redis will log that a script is +# still in execution after the maximum allowed time and will start to +# reply to queries with an error. +# +# When a long running script exceeds the maximum execution time only the +# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be +# used to stop a script that did not yet call any write commands. The second +# is the only way to shut down the server in the case a write command was +# already issued by the script but the user doesn't want to wait for the natural +# termination of the script. +# +# Set it to 0 or a negative value for unlimited execution without warnings. +lua-time-limit 5000 + +################################ REDIS CLUSTER ############################### + +# Normal Redis instances can't be part of a Redis Cluster; only nodes that are +# started as cluster nodes can. In order to start a Redis instance as a +# cluster node enable the cluster support uncommenting the following: +# +cluster-enabled yes + +# Every cluster node has a cluster configuration file. This file is not +# intended to be edited by hand. It is created and updated by Redis nodes. +# Every Redis Cluster node requires a different cluster configuration file. +# Make sure that instances running in the same system do not have +# overlapping cluster configuration file names. +# +# cluster-config-file /etc/data/nodes.conf + +# Cluster node timeout is the amount of milliseconds a node must be unreachable +# for it to be considered in failure state. +# Most other internal time limits are a multiple of the node timeout. +# +cluster-node-timeout 15000 + +# A replica of a failing master will avoid to start a failover if its data +# looks too old. +# +# There is no simple way for a replica to actually have an exact measure of +# its "data age", so the following two checks are performed: +# +# 1) If there are multiple replicas able to failover, they exchange messages +# in order to try to give an advantage to the replica with the best +# replication offset (more data from the master processed). +# Replicas will try to get their rank by offset, and apply to the start +# of the failover a delay proportional to their rank. +# +# 2) Every single replica computes the time of the last interaction with +# its master. This can be the last ping or command received (if the master +# is still in the "connected" state), or the time that elapsed since the +# disconnection with the master (if the replication link is currently down). +# If the last interaction is too old, the replica will not try to failover +# at all. +# +# The point "2" can be tuned by user. Specifically a replica will not perform +# the failover if, since the last interaction with the master, the time +# elapsed is greater than: +# +# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period +# +# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor +# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the +# replica will not try to failover if it was not able to talk with the master +# for longer than 310 seconds. +# +# A large cluster-replica-validity-factor may allow replicas with too old data to failover +# a master, while a too small value may prevent the cluster from being able to +# elect a replica at all. +# +# For maximum availability, it is possible to set the cluster-replica-validity-factor +# to a value of 0, which means, that replicas will always try to failover the +# master regardless of the last time they interacted with the master. +# (However they'll always try to apply a delay proportional to their +# offset rank). +# +# Zero is the only value able to guarantee that when all the partitions heal +# the cluster will always be able to continue. +# +# cluster-replica-validity-factor 10 + +# Cluster replicas are able to migrate to orphaned masters, that are masters +# that are left without working replicas. This improves the cluster ability +# to resist to failures as otherwise an orphaned master can't be failed over +# in case of failure if it has no working replicas. +# +# Replicas migrate to orphaned masters only if there are still at least a +# given number of other working replicas for their old master. This number +# is the "migration barrier". A migration barrier of 1 means that a replica +# will migrate only if there is at least 1 other working replica for its master +# and so forth. It usually reflects the number of replicas you want for every +# master in your cluster. +# +# Default is 1 (replicas migrate only if their masters remain with at least +# one replica). To disable migration just set it to a very large value. +# A value of 0 can be set but is useful only for debugging and dangerous +# in production. +# +# cluster-migration-barrier 1 + +# By default Redis Cluster nodes stop accepting queries if they detect there +# is at least a hash slot uncovered (no available node is serving it). +# This way if the cluster is partially down (for example a range of hash slots +# are no longer covered) all the cluster becomes, eventually, unavailable. +# It automatically returns available as soon as all the slots are covered again. +# +# However sometimes you want the subset of the cluster which is working, +# to continue to accept queries for the part of the key space that is still +# covered. In order to do so, just set the cluster-require-full-coverage +# option to no. +# +# cluster-require-full-coverage yes + +# This option, when set to yes, prevents replicas from trying to failover its +# master during master failures. However the master can still perform a +# manual failover, if forced to do so. +# +# This is useful in different scenarios, especially in the case of multiple +# data center operations, where we want one side to never be promoted if not +# in the case of a total DC failure. +# +# cluster-replica-no-failover no + +# This option, when set to yes, allows nodes to serve read traffic while the +# the cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful for two cases. The first case is for when an application +# doesn't require consistency of data during node failures or network partitions. +# One example of this is a cache, where as long as the node has the data it +# should be able to serve it. +# +# The second use case is for configurations that don't meet the recommended +# three shards but want to enable cluster mode and scale later. A +# master outage in a 1 or 2 shard configuration causes a read/write outage to the +# entire cluster without this option set, with it set there is only a write outage. +# Without a quorum of masters, slot ownership will not change automatically. +# +# cluster-allow-reads-when-down no + +# In order to setup your cluster make sure to read the documentation +# available at http://redis.io web site. + +########################## CLUSTER DOCKER/NAT support ######################## + +# In certain deployments, Redis Cluster nodes address discovery fails, because +# addresses are NAT-ted or because ports are forwarded (the typical case is +# Docker and other containers). +# +# In order to make Redis Cluster working in such environments, a static +# configuration where each node knows its public address is needed. The +# following two options are used for this scope, and are: +# +# * cluster-announce-ip +# * cluster-announce-port +# * cluster-announce-bus-port +# +# Each instructs the node about its address, client port, and cluster message +# bus port. The information is then published in the header of the bus packets +# so that other nodes will be able to correctly map the address of the node +# publishing the information. +# +# If the above options are not used, the normal Redis Cluster auto-detection +# will be used instead. +# +# Note that when remapped, the bus port may not be at the fixed offset of +# clients port + 10000, so you can specify any port and bus-port depending +# on how they get remapped. If the bus-port is not set, a fixed offset of +# 10000 will be used as usual. +# +# Example: +# +# cluster-announce-ip 10.1.1.5 +# cluster-announce-port 6379 +# cluster-announce-bus-port 6380 + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +################################ LATENCY MONITOR ############################## + +# The Redis latency monitoring subsystem samples different operations +# at runtime in order to collect data related to possible sources of +# latency of a Redis instance. +# +# Via the LATENCY command this information is available to the user that can +# print graphs and obtain reports. +# +# The system only logs operations that were performed in a time equal or +# greater than the amount of milliseconds specified via the +# latency-monitor-threshold configuration directive. When its value is set +# to zero, the latency monitor is turned off. +# +# By default latency monitoring is disabled since it is mostly not needed +# if you don't have latency issues, and collecting data has a performance +# impact, that while very small, can be measured under big load. Latency +# monitoring can easily be enabled at runtime using the command +# "CONFIG SET latency-monitor-threshold " if needed. +latency-monitor-threshold 0 + +############################# EVENT NOTIFICATION ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at http://redis.io/topics/notifications +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# It is possible to select the events that Redis will notify among a set +# of classes. Every class is identified by a single character: +# +# K Keyspace events, published with __keyspace@__ prefix. +# E Keyevent events, published with __keyevent@__ prefix. +# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... +# $ String commands +# l List commands +# s Set commands +# h Hash commands +# z Sorted set commands +# x Expired events (events generated every time a key expires) +# e Evicted events (events generated when a key is evicted for maxmemory) +# t Stream commands +# m Key-miss events (Note: It is not included in the 'A' class) +# A Alias for g$lshzxet, so that the "AKE" string means all the events +# (Except key-miss events which are excluded from 'A' due to their +# unique nature). +# +# The "notify-keyspace-events" takes as argument a string that is composed +# of zero or multiple characters. The empty string means that notifications +# are disabled. +# +# Example: to enable list and generic events, from the point of view of the +# event name, use: +# +# notify-keyspace-events Elg +# +# Example 2: to get the stream of the expired keys subscribing to channel +# name __keyevent@0__:expired use: +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +############################### GOPHER SERVER ################################# + +# Redis contains an implementation of the Gopher protocol, as specified in +# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt). +# +# The Gopher protocol was very popular in the late '90s. It is an alternative +# to the web, and the implementation both server and client side is so simple +# that the Redis server has just 100 lines of code in order to implement this +# support. +# +# What do you do with Gopher nowadays? Well Gopher never *really* died, and +# lately there is a movement in order for the Gopher more hierarchical content +# composed of just plain text documents to be resurrected. Some want a simpler +# internet, others believe that the mainstream internet became too much +# controlled, and it's cool to create an alternative space for people that +# want a bit of fresh air. +# +# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol +# as a gift. +# +# --- HOW IT WORKS? --- +# +# The Redis Gopher support uses the inline protocol of Redis, and specifically +# two kind of inline requests that were anyway illegal: an empty request +# or any request that starts with "/" (there are no Redis commands starting +# with such a slash). Normal RESP2/RESP3 requests are completely out of the +# path of the Gopher protocol implementation and are served as usual as well. +# +# If you open a connection to Redis when Gopher is enabled and send it +# a string like "/foo", if there is a key named "/foo" it is served via the +# Gopher protocol. +# +# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher +# talking), you likely need a script like the following: +# +# https://github.com/antirez/gopher2redis +# +# --- SECURITY WARNING --- +# +# If you plan to put Redis on the internet in a publicly accessible address +# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance. +# Once a password is set: +# +# 1. The Gopher server (when enabled, not by default) will still serve +# content via Gopher. +# 2. However other commands cannot be called before the client will +# authenticate. +# +# So use the 'requirepass' option to protect your instance. +# +# Note that Gopher is not currently supported when 'io-threads-do-reads' +# is enabled. +# +# To enable Gopher support, uncomment the following line and set the option +# from no (the default) to yes. +# +# gopher-enabled no + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +# Lists are also encoded in a special way to save a lot of space. +# The number of entries allowed per internal list node can be specified +# as a fixed maximum size or a maximum number of elements. +# For a fixed maximum size, use -5 through -1, meaning: +# -5: max size: 64 Kb <-- not recommended for normal workloads +# -4: max size: 32 Kb <-- not recommended +# -3: max size: 16 Kb <-- probably not recommended +# -2: max size: 8 Kb <-- good +# -1: max size: 4 Kb <-- good +# Positive numbers mean store up to _exactly_ that number of elements +# per list node. +# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), +# but if your use case is unique, adjust the settings as necessary. +list-max-ziplist-size -2 + +# Lists may also be compressed. +# Compress depth is the number of quicklist ziplist nodes from *each* side of +# the list to *exclude* from compression. The head and tail of the list +# are always uncompressed for fast push/pop operations. Settings are: +# 0: disable all list compression +# 1: depth 1 means "don't start compressing until after 1 node into the list, +# going from either the head or tail" +# So: [head]->node->node->...->node->[tail] +# [head], [tail] will always be uncompressed; inner nodes will compress. +# 2: [head]->[next]->node->node->...->node->[prev]->[tail] +# 2 here means: don't compress head or head->next or tail->prev or tail, +# but compress all nodes between them. +# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] +# etc. +list-compress-depth 0 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happen to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When an HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Streams macro node max size / items. The stream data structure is a radix +# tree of big nodes that encode multiple items inside. Using this configuration +# it is possible to configure how big a single node can be in bytes, and the +# maximum number of items it may contain before switching to a new node when +# appending new stream entries. If any of the following settings are set to +# zero, the limit is ignored, so for instance it is possible to set just a +# max entires limit by setting max-bytes to 0 and max-entries to the desired +# value. +stream-node-max-bytes 4096 +stream-node-max-entries 100 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# actively rehash the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply from time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients including MONITOR clients +# replica -> replica clients +# pubsub -> clients subscribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and replica clients, since +# subscribers and replicas receive data in a push fashion. +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Client query buffers accumulate new commands. They are limited to a fixed +# amount by default in order to avoid that a protocol desynchronization (for +# instance due to a bug in the client) will lead to unbound memory usage in +# the query buffer. However you can configure it here if you have very special +# needs, such us huge multi/exec requests or alike. +# +# client-query-buffer-limit 1gb + +# In the Redis protocol, bulk requests, that are, elements representing single +# strings, are normally limited to 512 mb. However you can change this limit +# here, but must be 1mb or greater +# +# proto-max-bulk-len 512mb + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform according to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 10 + +# Normally it is useful to have an HZ value which is proportional to the +# number of clients connected. This is useful in order, for instance, to +# avoid too many clients are processed for each background task invocation +# in order to avoid latency spikes. +# +# Since the default HZ value by default is conservatively set to 10, Redis +# offers, and enables by default, the ability to use an adaptive HZ value +# which will temporarily raise when there are many connected clients. +# +# When dynamic HZ is enabled, the actual configured HZ will be used +# as a baseline, but multiples of the configured HZ value will be actually +# used as needed once more clients are connected. In this way an idle +# instance will use very little CPU time while a busy instance will be +# more responsive. +dynamic-hz yes + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 32 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + +# When redis saves RDB file, if the following option is enabled +# the file will be fsync-ed every 32 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +rdb-save-incremental-fsync yes + +# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good +# idea to start with the default settings and only change them after investigating +# how to improve the performances and how the keys LFU change over time, which +# is possible to inspect via the OBJECT FREQ command. +# +# There are two tunable parameters in the Redis LFU implementation: the +# counter logarithm factor and the counter decay time. It is important to +# understand what the two parameters mean before changing them. +# +# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis +# uses a probabilistic increment with logarithmic behavior. Given the value +# of the old counter, when a key is accessed, the counter is incremented in +# this way: +# +# 1. A random number R between 0 and 1 is extracted. +# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). +# 3. The counter is incremented only if R < P. +# +# The default lfu-log-factor is 10. This is a table of how the frequency +# counter changes with a different number of accesses with different +# logarithmic factors: +# +# +--------+------------+------------+------------+------------+------------+ +# | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | +# +--------+------------+------------+------------+------------+------------+ +# | 0 | 104 | 255 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 1 | 18 | 49 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 10 | 10 | 18 | 142 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 100 | 8 | 11 | 49 | 143 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# +# NOTE: The above table was obtained by running the following commands: +# +# redis-benchmark -n 1000000 incr foo +# redis-cli object freq foo +# +# NOTE 2: The counter initial value is 5 in order to give new objects a chance +# to accumulate hits. +# +# The counter decay time is the time, in minutes, that must elapse in order +# for the key counter to be divided by two (or decremented if it has a value +# less <= 10). +# +# The default value for the lfu-decay-time is 1. A special value of 0 means to +# decay the counter every time it happens to be scanned. +# +# lfu-log-factor 10 +# lfu-decay-time 1 + +########################### ACTIVE DEFRAGMENTATION ####################### +# +# What is active defragmentation? +# ------------------------------- +# +# Active (online) defragmentation allows a Redis server to compact the +# spaces left between small allocations and deallocations of data in memory, +# thus allowing to reclaim back memory. +# +# Fragmentation is a natural process that happens with every allocator (but +# less so with Jemalloc, fortunately) and certain workloads. Normally a server +# restart is needed in order to lower the fragmentation, or at least to flush +# away all the data and create it again. However thanks to this feature +# implemented by Oran Agra for Redis 4.0 this process can happen at runtime +# in a "hot" way, while the server is running. +# +# Basically when the fragmentation is over a certain level (see the +# configuration options below) Redis will start to create new copies of the +# values in contiguous memory regions by exploiting certain specific Jemalloc +# features (in order to understand if an allocation is causing fragmentation +# and to allocate it in a better place), and at the same time, will release the +# old copies of the data. This process, repeated incrementally for all the keys +# will cause the fragmentation to drop back to normal values. +# +# Important things to understand: +# +# 1. This feature is disabled by default, and only works if you compiled Redis +# to use the copy of Jemalloc we ship with the source code of Redis. +# This is the default with Linux builds. +# +# 2. You never need to enable this feature if you don't have fragmentation +# issues. +# +# 3. Once you experience fragmentation, you can enable this feature when +# needed with the command "CONFIG SET activedefrag yes". +# +# The configuration parameters are able to fine tune the behavior of the +# defragmentation process. If you are not sure about what they mean it is +# a good idea to leave the defaults untouched. + +# Enabled active defragmentation +# activedefrag no + +# Minimum amount of fragmentation waste to start active defrag +# active-defrag-ignore-bytes 100mb + +# Minimum percentage of fragmentation to start active defrag +# active-defrag-threshold-lower 10 + +# Maximum percentage of fragmentation at which we use maximum effort +# active-defrag-threshold-upper 100 + +# Minimal effort for defrag in CPU percentage, to be used when the lower +# threshold is reached +# active-defrag-cycle-min 1 + +# Maximal effort for defrag in CPU percentage, to be used when the upper +# threshold is reached +# active-defrag-cycle-max 25 + +# Maximum number of set/hash/zset/list fields that will be processed from +# the main dictionary scan +# active-defrag-max-scan-fields 1000 + +# Jemalloc background thread for purging will be enabled by default +jemalloc-bg-thread yes + +# It is possible to pin different threads and processes of Redis to specific +# CPUs in your system, in order to maximize the performances of the server. +# This is useful both in order to pin different Redis threads in different +# CPUs, but also in order to make sure that multiple Redis instances running +# in the same host will be pinned to different CPUs. +# +# Normally you can do this using the "taskset" command, however it is also +# possible to this via Redis configuration directly, both in Linux and FreeBSD. +# +# You can pin the server/IO threads, bio threads, aof rewrite child process, and +# the bgsave child process. The syntax to specify the cpu list is the same as +# the taskset command: +# +# Set redis server/io threads to cpu affinity 0,2,4,6: +# server_cpulist 0-7:2 +# +# Set bio threads to cpu affinity 1,3: +# bio_cpulist 1,3 +# +# Set aof rewrite child process to cpu affinity 8,9,10,11: +# aof_rewrite_cpulist 8-11 +# +# Set bgsave child process to cpu affinity 1,10,11 +# bgsave_cpulist 1,10-11 + +# In some cases redis will emit warnings and even refuse to start if it detects +# that the system is in bad state, it is possible to suppress these warnings +# by setting the following config which takes a space delimited list of warnings +# to suppress +# +# ignore-warnings ARM64-COW-BUG diff --git a/tests/playwright/rte/oss-cluster-7/Dockerfile b/tests/playwright/rte/oss-cluster-7/Dockerfile new file mode 100644 index 0000000000..3c17583efd --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7/Dockerfile @@ -0,0 +1,11 @@ +FROM redislabs/rejson:1.0.8 as rejson + +FROM redislabs/redisearch:2.0.13 as redisearch + +FROM redis:7.0.0 + +COPY redis.conf /etc/redis/ +COPY --from=rejson /usr/lib/redis/modules/rejson.so /etc/redis/modules/ +COPY --from=redisearch /usr/lib/redis/modules/redisearch.so /etc/redis/modules/ + +CMD [ "redis-server", "/etc/redis/redis.conf" ] diff --git a/tests/playwright/rte/oss-cluster-7/cluster-create.sh b/tests/playwright/rte/oss-cluster-7/cluster-create.sh new file mode 100644 index 0000000000..5456f68479 --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7/cluster-create.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +echo 'Try to sleep for a while...' +sleep 25 +echo 'Creating cluster...' +echo "yes" | redis-cli \ + --cluster create \ + 172.31.100.211:6379 \ + 172.31.100.212:6379 \ + 172.31.100.213:6379 \ + --cluster-replicas 0 \ + && redis-server diff --git a/tests/playwright/rte/oss-cluster-7/creator.Dockerfile b/tests/playwright/rte/oss-cluster-7/creator.Dockerfile new file mode 100644 index 0000000000..4a8295321c --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7/creator.Dockerfile @@ -0,0 +1,9 @@ +FROM redis:7.0.0 + +USER root + +COPY cluster-create.sh ./ + +RUN chmod a+x cluster-create.sh + +CMD ["/bin/sh", "./cluster-create.sh"] diff --git a/tests/playwright/rte/oss-cluster-7/redis.conf b/tests/playwright/rte/oss-cluster-7/redis.conf new file mode 100644 index 0000000000..3733dcdd32 --- /dev/null +++ b/tests/playwright/rte/oss-cluster-7/redis.conf @@ -0,0 +1,1881 @@ +# Redis configuration file example. +# +# Note that in order to read the configuration file, Redis must be +# started with the file path as first argument: +# +# ./redis-server /path/to/redis.conf + +# Note on units: when memory size is needed, it is possible to specify +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis servers but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Note that option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# include /path/to/local.conf +# include /path/to/other.conf + +################################## MODULES ##################################### + +# Load modules at startup. If the server is not able to load modules +# it will abort. It is possible to use multiple loadmodule directives. +# +loadmodule /etc/redis/modules/rejson.so +loadmodule /etc/redis/modules/redisearch.so +# loadmodule /path/to/other_module.so + +################################## NETWORK ##################################### + +# By default, if no "bind" configuration directive is specified, Redis listens +# for connections from all available network interfaces on the host machine. +# It is possible to listen to just one or multiple selected interfaces using +# the "bind" configuration directive, followed by one or more IP addresses. +# +# Examples: +# +# bind 192.168.1.100 10.0.0.1 +# bind 127.0.0.1 ::1 +# +# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the +# internet, binding to all the interfaces is dangerous and will expose the +# instance to everybody on the internet. So by default we uncomment the +# following bind directive, that will force Redis to listen only on the +# IPv4 loopback interface address (this means Redis will only be able to +# accept client connections from the same host that it is running on). +# +# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES +# JUST COMMENT OUT THE FOLLOWING LINE. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# bind 127.0.0.1 + +# Protected mode is a layer of security protection, in order to avoid that +# Redis instances left open on the internet are accessed and exploited. +# +# When protected mode is on and if: +# +# 1) The server is not binding explicitly to a set of addresses using the +# "bind" directive. +# 2) No password is configured. +# +# The server only accepts connections from clients connecting from the +# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain +# sockets. +# +# By default protected mode is enabled. You should disable it only if +# you are sure you want clients from other hosts to connect to Redis +# even if no authentication is configured, nor a specific set of interfaces +# are explicitly listed using the "bind" directive. +# protected-mode yes + +# Accept connections on the specified port, default is 6379 (IANA #815344). +# If port 0 is specified Redis will not listen on a TCP socket. +port 6379 + +# TCP listen() backlog. +# +# In high requests-per-second environments you need a high backlog in order +# to avoid slow clients connection issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# Unix socket. +# +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +# unixsocket /tmp/redis.sock +# unixsocketperm 700 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# +# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence +# of communication. This is useful for two reasons: +# +# 1) Detect dead peers. +# 2) Force network equipment in the middle to consider the connection to be +# alive. +# +# On Linux, the specified value (in seconds) is the period used to send ACKs. +# Note that to close the connection the double of the time is needed. +# On other kernels the period depends on the kernel configuration. +# +# A reasonable value for this option is 300 seconds, which is the new +# Redis default starting with Redis 3.2.1. +tcp-keepalive 300 + +################################# TLS/SSL ##################################### + +# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration +# directive can be used to define TLS-listening ports. To enable TLS on the +# default port, use: +# +# port 0 +# tls-port 6379 + +# Configure a X.509 certificate and private key to use for authenticating the +# server to connected clients, masters or cluster peers. These files should be +# PEM formatted. +# +# tls-cert-file /etc/redis/redis.crt +# tls-key-file /etc/redis/redis.key + +# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: +# +# tls-dh-params-file redis.dh + +# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL +# clients and peers. Redis requires an explicit configuration of at least one +# of these, and will not implicitly use the system wide configuration. +# +# tls-ca-cert-file /etc/redis/ca.crt +# tls-ca-cert-dir /etc/ssl/certs + +# By default, clients (including replica servers) on a TLS port are required +# to authenticate using valid client side certificates. +# +# If "no" is specified, client certificates are not required and not accepted. +# If "optional" is specified, client certificates are accepted and must be +# valid if provided, but are not required. +# +# tls-auth-clients yes +# tls-auth-clients optional + +# By default, a Redis replica does not attempt to establish a TLS connection +# with its master. +# +# Use the following directive to enable TLS on replication links. +# +# tls-replication yes + +# By default, the Redis Cluster bus uses a plain TCP connection. To enable +# TLS for the bus protocol, use the following directive: +# +# tls-cluster yes + +# Explicitly specify TLS versions to support. Allowed values are case insensitive +# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or +# any combination. To enable only TLSv1.2 and TLSv1.3, use: +# +# tls-protocols "TLSv1.2 TLSv1.3" + +# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information +# about the syntax of this string. +# +# Note: this configuration applies only to <= TLSv1.2. +# +# tls-ciphers DEFAULT:!MEDIUM + +# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more +# information about the syntax of this string, and specifically for TLSv1.3 +# ciphersuites. +# +# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 + +# When choosing a cipher, use the server's preference instead of the client +# preference. By default, the server follows the client's preference. +# +# tls-prefer-server-ciphers yes + +# By default, TLS session caching is enabled to allow faster and less expensive +# reconnections by clients that support it. Use the following directive to disable +# caching. +# +# tls-session-caching no + +# Change the default number of TLS sessions cached. A zero value sets the cache +# to unlimited size. The default size is 20480. +# +# tls-session-cache-size 5000 + +# Change the default timeout of cached TLS sessions. The default timeout is 300 +# seconds. +# +# tls-session-cache-timeout 60 + +################################# GENERAL ##################################### + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize no + +# If you run Redis from upstart or systemd, Redis can interact with your +# supervision tree. Options: +# supervised no - no supervision interaction +# supervised upstart - signal upstart by putting Redis into SIGSTOP mode +# requires "expect stop" in your upstart job config +# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET +# supervised auto - detect upstart or systemd method based on +# UPSTART_JOB or NOTIFY_SOCKET environment variables +# Note: these supervision methods only signal "process is ready." +# They do not enable continuous pings back to your supervisor. +supervised no + +# If a pid file is specified, Redis writes it where specified at startup +# and removes it at exit. +# +# When the server runs non daemonized, no pid file is created if none is +# specified in the configuration. When the server is daemonized, the pid file +# is used even if not specified, defaulting to "/var/run/redis.pid". +# +# Creating a pid file is best effort: if Redis is not able to create it +# nothing bad happens, the server will start and run normally. +pidfile /var/run/redis_6379.pid + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel notice + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile "" + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +# By default Redis shows an ASCII art logo only when started to log to the +# standard output and if the standard output is a TTY. Basically this means +# that normally a logo is displayed only in interactive sessions. +# +# However it is possible to force the pre-4.0 behavior and always show a +# ASCII art logo in startup logs by setting the following option to yes. +always-show-logo yes + +################################ SNAPSHOTTING ################################ +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behavior will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving completely by commenting out all "save" lines. +# +# It is also possible to remove all the previously configured save +# points by adding a save directive with a single empty string argument +# like in the following example: +# +# save "" + +save 900 1 +save 300 10 +save 60 10000 + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# By default compression is enabled as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + +# The filename where to dump the DB +dbfilename dump.rdb + +# Remove RDB files used by replication in instances without persistence +# enabled. By default this option is disabled, however there are environments +# where for regulations or other security concerns, RDB files persisted on +# disk by masters in order to feed replicas, or stored on disk by replicas +# in order to load them for the initial synchronization, should be deleted +# ASAP. Note that this option ONLY WORKS in instances that have both AOF +# and RDB persistence disabled, otherwise is completely ignored. +# +# An alternative (and sometimes better) way to obtain the same effect is +# to use diskless replication on both master and replicas instances. However +# in the case of replicas, diskless is not always an option. +# in the case of replicas, diskless is not always an option. +rdb-del-sync-files no + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Replica replication. Use replicaof to make a Redis instance a copy of +# another Redis server. A few things to understand ASAP about Redis replication. +# +# +------------------+ +---------------+ +# | Master | ---> | Replica | +# | (receive writes) | | (exact copy) | +# +------------------+ +---------------+ +# +# 1) Redis replication is asynchronous, but you can configure a master to +# stop accepting writes if it appears to be not connected with at least +# a given number of replicas. +# 2) Redis replicas are able to perform a partial resynchronization with the +# master if the replication link is lost for a relatively small amount of +# time. You may want to configure the replication backlog size (see the next +# sections of this file) with a sensible value depending on your needs. +# 3) Replication is automatic and does not need user intervention. After a +# network partition replicas automatically try to reconnect to masters +# and resynchronize with them. +# +# replicaof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the replica to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the replica request. +# +# masterauth defaultpass +# +# However this is not enough if you are using Redis ACLs (for Redis version +# 6 or greater), and the default user is not capable of running the PSYNC +# command and/or other commands needed for replication. In this case it's +# better to configure a special user to use with replication, and specify the +# masteruser configuration as such: +# +# masteruser +# +# When masteruser is specified, the replica will authenticate against its +# master using the new AUTH form: AUTH . + +# When a replica loses its connection with the master, or when the replication +# is still in progress, the replica can act in two different ways: +# +# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) If replica-serve-stale-data is set to 'no' the replica will reply with +# an error "SYNC with master in progress" to all commands except: +# INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, +# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, +# HOST and LATENCY. +# +replica-serve-stale-data yes + +# You can configure a replica instance to accept writes or not. Writing against +# a replica instance may be useful to store some ephemeral data (because data +# written on a replica will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default replicas are read-only. +# +# Note: read only replicas are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only replica exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only replicas using 'rename-command' to shadow all the +# administrative / dangerous commands. +replica-read-only yes + +# Replication SYNC strategy: disk or socket. +# +# New replicas and reconnecting replicas that are not able to continue the +# replication process just receiving differences, need to do what is called a +# "full synchronization". An RDB file is transmitted from the master to the +# replicas. +# +# The transmission can happen in two different ways: +# +# 1) Disk-backed: The Redis master creates a new process that writes the RDB +# file on disk. Later the file is transferred by the parent +# process to the replicas incrementally. +# 2) Diskless: The Redis master creates a new process that directly writes the +# RDB file to replica sockets, without touching the disk at all. +# +# With disk-backed replication, while the RDB file is generated, more replicas +# can be queued and served with the RDB file as soon as the current child +# producing the RDB file finishes its work. With diskless replication instead +# once the transfer starts, new replicas arriving will be queued and a new +# transfer will start when the current one terminates. +# +# When diskless replication is used, the master waits a configurable amount of +# time (in seconds) before starting the transfer in the hope that multiple +# replicas will arrive and the transfer can be parallelized. +# +# With slow disks and fast (large bandwidth) networks, diskless replication +# works better. +repl-diskless-sync no + +# When diskless replication is enabled, it is possible to configure the delay +# the server waits in order to spawn the child that transfers the RDB via socket +# to the replicas. +# +# This is important since once the transfer starts, it is not possible to serve +# new replicas arriving, that will be queued for the next RDB transfer, so the +# server waits a delay in order to let more replicas arrive. +# +# The delay is specified in seconds, and by default is 5 seconds. To disable +# it entirely just set it to 0 seconds and the transfer will start ASAP. +repl-diskless-sync-delay 5 + +# ----------------------------------------------------------------------------- +# WARNING: RDB diskless load is experimental. Since in this setup the replica +# does not immediately store an RDB on disk, it may cause data loss during +# failovers. RDB diskless load + Redis modules not handling I/O reads may also +# cause Redis to abort in case of I/O errors during the initial synchronization +# stage with the master. Use only if your do what you are doing. +# ----------------------------------------------------------------------------- +# +# Replica can load the RDB it reads from the replication link directly from the +# socket, or store the RDB to a file and read that file after it was completely +# received from the master. +# +# In many cases the disk is slower than the network, and storing and loading +# the RDB file may increase replication time (and even increase the master's +# Copy on Write memory and salve buffers). +# However, parsing the RDB file directly from the socket may mean that we have +# to flush the contents of the current database before the full rdb was +# received. For this reason we have the following options: +# +# "disabled" - Don't use diskless load (store the rdb file to the disk first) +# "on-empty-db" - Use diskless load only when it is completely safe. +# "swapdb" - Keep a copy of the current db contents in RAM while parsing +# the data directly from the socket. note that this requires +# sufficient memory, if you don't have it, you risk an OOM kill. +repl-diskless-load disabled + +# Replicas send PINGs to server in a predefined interval. It's possible to +# change this interval with the repl_ping_replica_period option. The default +# value is 10 seconds. +# +# repl-ping-replica-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of replica. +# 2) Master timeout from the point of view of replicas (data, pings). +# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-replica-period otherwise a timeout will be detected +# every time there is low traffic between the master and the replica. The default +# value is 60 seconds. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the replica socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to replicas. But this can add a delay for +# the data to appear on the replica side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the replica side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and replicas are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# Set the replication backlog size. The backlog is a buffer that accumulates +# replica data when replicas are disconnected for some time, so that when a +# replica wants to reconnect again, often a full resync is not needed, but a +# partial resync is enough, just passing the portion of data the replica +# missed while disconnected. +# +# The bigger the replication backlog, the longer the replica can endure the +# disconnect and later be able to perform a partial resynchronization. +# +# The backlog is only allocated if there is at least one replica connected. +# +# repl-backlog-size 1mb + +# After a master has no connected replicas for some time, the backlog will be +# freed. The following option configures the amount of seconds that need to +# elapse, starting from the time the last replica disconnected, for the backlog +# buffer to be freed. +# +# Note that replicas never free the backlog for timeout, since they may be +# promoted to masters later, and should be able to correctly "partially +# resynchronize" with other replicas: hence they should always accumulate backlog. +# +# A value of 0 means to never release the backlog. +# +# repl-backlog-ttl 3600 + +# The replica priority is an integer number published by Redis in the INFO +# output. It is used by Redis Sentinel in order to select a replica to promote +# into a master if the master is no longer working correctly. +# +# A replica with a low priority number is considered better for promotion, so +# for instance if there are three replicas with priority 10, 100, 25 Sentinel +# will pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the replica as not able to perform the +# role of master, so a replica with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +replica-priority 100 + +# It is possible for a master to stop accepting writes if there are less than +# N replicas connected, having a lag less or equal than M seconds. +# +# The N replicas need to be in "online" state. +# +# The lag in seconds, that must be <= the specified value, is calculated from +# the last ping received from the replica, that is usually sent every second. +# +# This option does not GUARANTEE that N replicas will accept the write, but +# will limit the window of exposure for lost writes in case not enough replicas +# are available, to the specified number of seconds. +# +# For example to require at least 3 replicas with a lag <= 10 seconds use: +# +# min-replicas-to-write 3 +# min-replicas-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-replicas-to-write is set to 0 (feature disabled) and +# min-replicas-max-lag is set to 10. + +# A Redis master is able to list the address and port of the attached +# replicas in different ways. For example the "INFO replication" section +# offers this information, which is used, among other tools, by +# Redis Sentinel in order to discover replica instances. +# Another place where this info is available is in the output of the +# "ROLE" command of a master. +# +# The listed IP address and port normally reported by a replica is +# obtained in the following way: +# +# IP: The address is auto detected by checking the peer address +# of the socket used by the replica to connect with the master. +# +# Port: The port is communicated by the replica during the replication +# handshake, and is normally the port that the replica is using to +# listen for connections. +# +# However when port forwarding or Network Address Translation (NAT) is +# used, the replica may actually be reachable via different IP and port +# pairs. The following two options can be used by a replica in order to +# report to its master a specific set of IP and port, so that both INFO +# and ROLE will report those values. +# +# There is no need to use both the options if you need to override just +# the port or the IP address. +# +# replica-announce-ip 5.5.5.5 +# replica-announce-port 1234 + +############################### KEYS TRACKING ################################# + +# Redis implements server assisted support for client side caching of values. +# This is implemented using an invalidation table that remembers, using +# 16 millions of slots, what clients may have certain subsets of keys. In turn +# this is used in order to send invalidation messages to clients. Please +# check this page to understand more about the feature: +# +# https://redis.io/topics/client-side-caching +# +# When tracking is enabled for a client, all the read only queries are assumed +# to be cached: this will force Redis to store information in the invalidation +# table. When keys are modified, such information is flushed away, and +# invalidation messages are sent to the clients. However if the workload is +# heavily dominated by reads, Redis could use more and more memory in order +# to track the keys fetched by many clients. +# +# For this reason it is possible to configure a maximum fill value for the +# invalidation table. By default it is set to 1M of keys, and once this limit +# is reached, Redis will start to evict keys in the invalidation table +# even if they were not modified, just to reclaim memory: this will in turn +# force the clients to invalidate the cached values. Basically the table +# maximum size is a trade off between the memory you want to spend server +# side to track information about who cached what, and the ability of clients +# to retain cached objects in memory. +# +# If you set the value to 0, it means there are no limits, and Redis will +# retain as many keys as needed in the invalidation table. +# In the "stats" INFO section, you can find information about the number of +# keys in the invalidation table at every given moment. +# +# Note: when key tracking is used in broadcasting mode, no memory is used +# in the server side so this setting is useless. +# +# tracking-table-max-keys 1000000 + +################################## SECURITY ################################### + +# Warning: since Redis is pretty fast, an outside user can try up to +# 1 million passwords per second against a modern box. This means that you +# should use very strong passwords, otherwise they will be very easy to break. +# Note that because the password is really a shared secret between the client +# and the server, and should not be memorized by any human, the password +# can be easily a long string from /dev/urandom or whatever, so by using a +# long and unguessable password no brute force attack will be possible. + +# Redis ACL users are defined in the following format: +# +# user ... acl rules ... +# +# For example: +# +# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 +# +# The special username "default" is used for new connections. If this user +# has the "nopass" rule, then new connections will be immediately authenticated +# as the "default" user without the need of any password provided via the +# AUTH command. Otherwise if the "default" user is not flagged with "nopass" +# the connections will start in not authenticated state, and will require +# AUTH (or the HELLO command AUTH option) in order to be authenticated and +# start to work. +# +# The ACL rules that describe what a user can do are the following: +# +# on Enable the user: it is possible to authenticate as this user. +# off Disable the user: it's no longer possible to authenticate +# with this user, however the already authenticated connections +# will still work. +# + Allow the execution of that command +# - Disallow the execution of that command +# +@ Allow the execution of all the commands in such category +# with valid categories are like @admin, @set, @sortedset, ... +# and so forth, see the full list in the server.c file where +# the Redis command table is described and defined. +# The special category @all means all the commands, but currently +# present in the server, and that will be loaded in the future +# via modules. +# +|subcommand Allow a specific subcommand of an otherwise +# disabled command. Note that this form is not +# allowed as negative like -DEBUG|SEGFAULT, but +# only additive starting with "+". +# allcommands Alias for +@all. Note that it implies the ability to execute +# all the future commands loaded via the modules system. +# nocommands Alias for -@all. +# ~ Add a pattern of keys that can be mentioned as part of +# commands. For instance ~* allows all the keys. The pattern +# is a glob-style pattern like the one of KEYS. +# It is possible to specify multiple patterns. +# allkeys Alias for ~* +# resetkeys Flush the list of allowed keys patterns. +# > Add this password to the list of valid password for the user. +# For example >mypass will add "mypass" to the list. +# This directive clears the "nopass" flag (see later). +# < Remove this password from the list of valid passwords. +# nopass All the set passwords of the user are removed, and the user +# is flagged as requiring no password: it means that every +# password will work against this user. If this directive is +# used for the default user, every new connection will be +# immediately authenticated with the default user without +# any explicit AUTH command required. Note that the "resetpass" +# directive will clear this condition. +# resetpass Flush the list of allowed passwords. Moreover removes the +# "nopass" status. After "resetpass" the user has no associated +# passwords and there is no way to authenticate without adding +# some password (or setting it as "nopass" later). +# reset Performs the following actions: resetpass, resetkeys, off, +# -@all. The user returns to the same state it has immediately +# after its creation. +# +# ACL rules can be specified in any order: for instance you can start with +# passwords, then flags, or key patterns. However note that the additive +# and subtractive rules will CHANGE MEANING depending on the ordering. +# For instance see the following example: +# +# user alice on +@all -DEBUG ~* >somepassword +# +# This will allow "alice" to use all the commands with the exception of the +# DEBUG command, since +@all added all the commands to the set of the commands +# alice can use, and later DEBUG was removed. However if we invert the order +# of two ACL rules the result will be different: +# +# user alice on -DEBUG +@all ~* >somepassword +# +# Now DEBUG was removed when alice had yet no commands in the set of allowed +# commands, later all the commands are added, so the user will be able to +# execute everything. +# +# Basically ACL rules are processed left-to-right. +# +# For more information about ACL configuration please refer to +# the Redis web site at https://redis.io/topics/acl + +# ACL LOG +# +# The ACL Log tracks failed commands and authentication events associated +# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked +# by ACLs. The ACL Log is stored in memory. You can reclaim memory with +# ACL LOG RESET. Define the maximum entry length of the ACL Log below. +acllog-max-len 128 + +# Using an external ACL file +# +# Instead of configuring users here in this file, it is possible to use +# a stand-alone file just listing users. The two methods cannot be mixed: +# if you configure users here and at the same time you activate the external +# ACL file, the server will refuse to start. +# +# The format of the external ACL user file is exactly the same as the +# format that is used inside redis.conf to describe users. +# +# aclfile /etc/redis/users.acl + +# aclfile /etc/redis/users.acl + +# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility +# layer on top of the new ACL system. The option effect will be just setting +# the password for the default user. Clients will still authenticate using +# AUTH as usually, or more explicitly with AUTH default +# if they follow the new protocol: both will work. +# +# requirepass somepass + +# Command renaming (DEPRECATED). +# +# ------------------------------------------------------------------------ +# WARNING: avoid using this option if possible. Instead use ACLs to remove +# commands from the default user, and put them only in some admin user you +# create for administrative purposes. +# ------------------------------------------------------------------------ +# +# It is possible to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# hard to guess so that it will still be available for internal-use tools +# but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possible to completely kill a command by renaming it into +# an empty string: +# +# rename-command CONFIG "" +# +# Please note that changing the name of commands that are logged into the +# AOF file or transmitted to replicas may cause problems. + +################################### CLIENTS #################################### + +# Set the max number of connected clients at the same time. By default +# this limit is set to 10000 clients, however if the Redis server is not +# able to configure the process file limit to allow for the specified limit +# the max number of allowed clients is set to the current file limit +# minus 32 (as Redis reserves a few file descriptors for internal uses). +# +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# IMPORTANT: When Redis Cluster is used, the max number of connections is also +# shared with the cluster bus: every node in the cluster will use two +# connections, one incoming and another outgoing. It is important to size the +# limit accordingly in case of very large clusters. +# +# maxclients 10000 + +############################## MEMORY MANAGEMENT ################################ + +# Set a memory usage limit to the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# according to the eviction policy selected (see maxmemory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU or LFU cache, or to +# set a hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have replicas attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the replicas are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of replicas is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have replicas attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for replica +# output buffers (but this is not needed if the policy is 'noeviction'). +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached. You can select one from the following behaviors: +# +# volatile-lru -> Evict using approximated LRU, only keys with an expire set. +# allkeys-lru -> Evict any key using approximated LRU. +# volatile-lfu -> Evict using approximated LFU, only keys with an expire set. +# allkeys-lfu -> Evict any key using approximated LFU. +# volatile-random -> Remove a random key having an expire set. +# allkeys-random -> Remove a random key, any key. +# volatile-ttl -> Remove the key with the nearest expire time (minor TTL) +# noeviction -> Don't evict anything, just return an error on write operations. +# +# LRU means Least Recently Used +# LFU means Least Frequently Used +# +# Both LRU, LFU and volatile-ttl are implemented using approximated +# randomized algorithms. +# +# Note: with any of the above policies, Redis will return an error on write +# operations, when there are no suitable keys for eviction. +# +# At the date of writing these commands are: set setnx setex append +# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd +# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby +# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby +# getset mset msetnx exec sort +# +# The default is: +# +# maxmemory-policy noeviction + +# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can tune it for speed or +# accuracy. By default Redis will check five keys and pick the one that was +# used least recently, you can change the sample size using the following +# configuration directive. +# +# The default of 5 produces good enough results. 10 Approximates very closely +# true LRU but costs more CPU. 3 is faster but not very accurate. +# +# maxmemory-samples 5 + +# Starting from Redis 5, by default a replica will ignore its maxmemory setting +# (unless it is promoted to master after a failover or manually). It means +# that the eviction of keys will be just handled by the master, sending the +# DEL commands to the replica as keys evict in the master side. +# +# This behavior ensures that masters and replicas stay consistent, and is usually +# what you want, however if your replica is writable, or you want the replica +# to have a different memory setting, and you are sure all the writes performed +# to the replica are idempotent, then you may change this default (but be sure +# to understand what you are doing). +# +# Note that since the replica by default does not evict, it may end using more +# memory than the one set via maxmemory (there are certain buffers that may +# be larger on the replica, or data structures may sometimes take more memory +# and so forth). So make sure you monitor your replicas and make sure they +# have enough memory to never hit a real out-of-memory condition before the +# master hits the configured maxmemory setting. +# +# replica-ignore-maxmemory yes + +# Redis reclaims expired keys in two ways: upon access when those keys are +# found to be expired, and also in background, in what is called the +# "active expire key". The key space is slowly and interactively scanned +# looking for expired keys to reclaim, so that it is possible to free memory +# of keys that are expired and will never be accessed again in a short time. +# +# The default effort of the expire cycle will try to avoid having more than +# ten percent of expired keys still in memory, and will try to avoid consuming +# more than 25% of total memory and to add latency to the system. However +# it is possible to increase the expire "effort" that is normally set to +# "1", to a greater value, up to the value "10". At its maximum value the +# system will use more CPU, longer cycles (and technically may introduce +# more latency), and will tolerate less already expired keys still present +# in the system. It's a tradeoff between memory, CPU and latency. +# +# active-expire-effort 1 + +############################# LAZY FREEING #################################### + +# Redis has two primitives to delete keys. One is called DEL and is a blocking +# deletion of the object. It means that the server stops processing new commands +# in order to reclaim all the memory associated with an object in a synchronous +# way. If the key deleted is associated with a small object, the time needed +# in order to execute the DEL command is very small and comparable to most other +# O(1) or O(log_N) commands in Redis. However if the key is associated with an +# aggregated value containing millions of elements, the server can block for +# a long time (even seconds) in order to complete the operation. +# +# For the above reasons Redis also offers non blocking deletion primitives +# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and +# FLUSHDB commands, in order to reclaim memory in background. Those commands +# are executed in constant time. Another thread will incrementally free the +# object in the background as fast as possible. +# +# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. +# It's up to the design of the application to understand when it is a good +# idea to use one or the other. However the Redis server sometimes has to +# delete keys or flush the whole database as a side effect of other operations. +# Specifically Redis deletes objects independently of a user call in the +# following scenarios: +# +# 1) On eviction, because of the maxmemory and maxmemory policy configurations, +# in order to make room for new data, without going over the specified +# memory limit. +# 2) Because of expire: when a key with an associated time to live (see the +# EXPIRE command) must be deleted from memory. +# 3) Because of a side effect of a command that stores data on a key that may +# already exist. For example the RENAME command may delete the old key +# content when it is replaced with another one. Similarly SUNIONSTORE +# or SORT with STORE option may delete existing keys. The SET command +# itself removes any old content of the specified key in order to replace +# it with the specified string. +# 4) During replication, when a replica performs a full resynchronization with +# its master, the content of the whole database is removed in order to +# load the RDB file just transferred. +# +# In all the above cases the default is to delete objects in a blocking way, +# like if DEL was called. However you can configure each case specifically +# in order to instead release memory in a non-blocking way like if UNLINK +# was called, using the following configuration directives. + +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no + +# It is also possible, for the case when to replace the user code DEL calls +# with UNLINK calls is not easy, to modify the default behavior of the DEL +# command to act exactly like UNLINK, using the following configuration +# directive: + +lazyfree-lazy-user-del no + +################################ THREADED I/O ################################# + +# Redis is mostly single threaded, however there are certain threaded +# operations such as UNLINK, slow I/O accesses and other things that are +# performed on side threads. +# +# Now it is also possible to handle Redis clients socket reads and writes +# in different I/O threads. Since especially writing is so slow, normally +# Redis users use pipelining in order to speed up the Redis performances per +# core, and spawn multiple instances in order to scale more. Using I/O +# threads it is possible to easily speedup two times Redis without resorting +# to pipelining nor sharding of the instance. +# +# By default threading is disabled, we suggest enabling it only in machines +# that have at least 4 or more cores, leaving at least one spare core. +# Using more than 8 threads is unlikely to help much. We also recommend using +# threaded I/O only if you actually have performance problems, with Redis +# instances being able to use a quite big percentage of CPU time, otherwise +# there is no point in using this feature. +# +# So for instance if you have a four cores boxes, try to use 2 or 3 I/O +# threads, if you have a 8 cores, try to use 6 threads. In order to +# enable I/O threads use the following configuration directive: +# +# io-threads 4 +# +# Setting io-threads to 1 will just use the main thread as usual. +# When I/O threads are enabled, we only use threads for writes, that is +# to thread the write(2) syscall and transfer the client buffers to the +# socket. However it is also possible to enable threading of reads and +# protocol parsing using the following configuration directive, by setting +# it to yes: +# +# io-threads-do-reads no +# +# Usually threading reads doesn't help much. +# +# NOTE 1: This configuration directive cannot be changed at runtime via +# CONFIG SET. Aso this feature currently does not work when SSL is +# enabled. +# +# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make +# sure you also run the benchmark itself in threaded mode, using the +# --threads option to match the number of Redis threads, otherwise you'll not +# be able to notice the improvements. + +############################ KERNEL OOM CONTROL ############################## + +# On Linux, it is possible to hint the kernel OOM killer on what processes +# should be killed first when out of memory. +# +# Enabling this feature makes Redis actively control the oom_score_adj value +# for all its processes, depending on their role. The default scores will +# attempt to have background child processes killed before all others, and +# replicas killed before masters. +# +# Redis supports three options: +# +# no: Don't make changes to oom-score-adj (default). +# yes: Alias to "relative" see below. +# absolute: Values in oom-score-adj-values are written as is to the kernel. +# relative: Values are used relative to the initial value of oom_score_adj when +# the server starts and are then clamped to a range of -1000 to 1000. +# Because typically the initial value is 0, they will often match the +# absolute values. +oom-score-adj no + +# When oom-score-adj is used, this directive controls the specific values used +# for master, replica and background child processes. Values range -2000 to +# 2000 (higher means more likely to be killed). +# +# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) +# can freely increase their value, but not decrease it below its initial +# settings. This means that setting oom-score-adj to "relative" and setting the +# oom-score-adj-values to positive values will always succeed. +oom-score-adj-values 0 200 800 + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check http://redis.io/topics/persistence for more information. + +appendonly no + +# The name of the append only file (default: "appendonly.aof") + +appendfilename "appendonly.aof" + +# The fsync() call tells the Operating System to actually write data on disk +# instead of waiting for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log. Slow, Safest. +# everysec: fsync only one time every second. Compromise. +# +# The default is "everysec", as that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving, the durability of Redis is +# the same as "appendfsync none". In practical terms, this means that it is +# possible to lose up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. + +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size grows by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (if no rewrite has happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a percentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +# An AOF file may be found to be truncated at the end during the Redis +# startup process, when the AOF data gets loaded back into memory. +# This may happen when the system where Redis is running +# crashes, especially when an ext4 filesystem is mounted without the +# data=ordered option (however this can't happen when Redis itself +# crashes or aborts but the operating system still works correctly). +# +# Redis can either exit with an error when this happens, or load as much +# data as possible (the default now) and start if the AOF file is found +# to be truncated at the end. The following option controls this behavior. +# +# If aof-load-truncated is set to yes, a truncated AOF file is loaded and +# the Redis server starts emitting a log to inform the user of the event. +# Otherwise if the option is set to no, the server aborts with an error +# and refuses to start. When the option is set to no, the user requires +# to fix the AOF file using the "redis-check-aof" utility before to restart +# the server. +# +# Note that if the AOF file will be found to be corrupted in the middle +# the server will still exit with an error. This option only applies when +# Redis will try to read more data from the AOF file but not enough bytes +# will be found. +aof-load-truncated yes + +# When rewriting the AOF file, Redis is able to use an RDB preamble in the +# AOF file for faster rewrites and recoveries. When this option is turned +# on the rewritten AOF file is composed of two different stanzas: +# +# [RDB file][AOF tail] +# +# When loading, Redis recognizes that the AOF file starts with the "REDIS" +# string and loads the prefixed RDB file, then continues loading the AOF +# tail. +aof-use-rdb-preamble yes + +################################ LUA SCRIPTING ############################### + +# Max execution time of a Lua script in milliseconds. +# +# If the maximum execution time is reached Redis will log that a script is +# still in execution after the maximum allowed time and will start to +# reply to queries with an error. +# +# When a long running script exceeds the maximum execution time only the +# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be +# used to stop a script that did not yet call any write commands. The second +# is the only way to shut down the server in the case a write command was +# already issued by the script but the user doesn't want to wait for the natural +# termination of the script. +# +# Set it to 0 or a negative value for unlimited execution without warnings. +lua-time-limit 5000 + +################################ REDIS CLUSTER ############################### + +# Normal Redis instances can't be part of a Redis Cluster; only nodes that are +# started as cluster nodes can. In order to start a Redis instance as a +# cluster node enable the cluster support uncommenting the following: +# +cluster-enabled yes + +# Every cluster node has a cluster configuration file. This file is not +# intended to be edited by hand. It is created and updated by Redis nodes. +# Every Redis Cluster node requires a different cluster configuration file. +# Make sure that instances running in the same system do not have +# overlapping cluster configuration file names. +# +# cluster-config-file /etc/data/nodes.conf + +# Cluster node timeout is the amount of milliseconds a node must be unreachable +# for it to be considered in failure state. +# Most other internal time limits are a multiple of the node timeout. +# +cluster-node-timeout 15000 + +# A replica of a failing master will avoid to start a failover if its data +# looks too old. +# +# There is no simple way for a replica to actually have an exact measure of +# its "data age", so the following two checks are performed: +# +# 1) If there are multiple replicas able to failover, they exchange messages +# in order to try to give an advantage to the replica with the best +# replication offset (more data from the master processed). +# Replicas will try to get their rank by offset, and apply to the start +# of the failover a delay proportional to their rank. +# +# 2) Every single replica computes the time of the last interaction with +# its master. This can be the last ping or command received (if the master +# is still in the "connected" state), or the time that elapsed since the +# disconnection with the master (if the replication link is currently down). +# If the last interaction is too old, the replica will not try to failover +# at all. +# +# The point "2" can be tuned by user. Specifically a replica will not perform +# the failover if, since the last interaction with the master, the time +# elapsed is greater than: +# +# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period +# +# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor +# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the +# replica will not try to failover if it was not able to talk with the master +# for longer than 310 seconds. +# +# A large cluster-replica-validity-factor may allow replicas with too old data to failover +# a master, while a too small value may prevent the cluster from being able to +# elect a replica at all. +# +# For maximum availability, it is possible to set the cluster-replica-validity-factor +# to a value of 0, which means, that replicas will always try to failover the +# master regardless of the last time they interacted with the master. +# (However they'll always try to apply a delay proportional to their +# offset rank). +# +# Zero is the only value able to guarantee that when all the partitions heal +# the cluster will always be able to continue. +# +# cluster-replica-validity-factor 10 + +# Cluster replicas are able to migrate to orphaned masters, that are masters +# that are left without working replicas. This improves the cluster ability +# to resist to failures as otherwise an orphaned master can't be failed over +# in case of failure if it has no working replicas. +# +# Replicas migrate to orphaned masters only if there are still at least a +# given number of other working replicas for their old master. This number +# is the "migration barrier". A migration barrier of 1 means that a replica +# will migrate only if there is at least 1 other working replica for its master +# and so forth. It usually reflects the number of replicas you want for every +# master in your cluster. +# +# Default is 1 (replicas migrate only if their masters remain with at least +# one replica). To disable migration just set it to a very large value. +# A value of 0 can be set but is useful only for debugging and dangerous +# in production. +# +# cluster-migration-barrier 1 + +# By default Redis Cluster nodes stop accepting queries if they detect there +# is at least a hash slot uncovered (no available node is serving it). +# This way if the cluster is partially down (for example a range of hash slots +# are no longer covered) all the cluster becomes, eventually, unavailable. +# It automatically returns available as soon as all the slots are covered again. +# +# However sometimes you want the subset of the cluster which is working, +# to continue to accept queries for the part of the key space that is still +# covered. In order to do so, just set the cluster-require-full-coverage +# option to no. +# +# cluster-require-full-coverage yes + +# This option, when set to yes, prevents replicas from trying to failover its +# master during master failures. However the master can still perform a +# manual failover, if forced to do so. +# +# This is useful in different scenarios, especially in the case of multiple +# data center operations, where we want one side to never be promoted if not +# in the case of a total DC failure. +# +# cluster-replica-no-failover no + +# This option, when set to yes, allows nodes to serve read traffic while the +# the cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful for two cases. The first case is for when an application +# doesn't require consistency of data during node failures or network partitions. +# One example of this is a cache, where as long as the node has the data it +# should be able to serve it. +# +# The second use case is for configurations that don't meet the recommended +# three shards but want to enable cluster mode and scale later. A +# master outage in a 1 or 2 shard configuration causes a read/write outage to the +# entire cluster without this option set, with it set there is only a write outage. +# Without a quorum of masters, slot ownership will not change automatically. +# +# cluster-allow-reads-when-down no + +# In order to setup your cluster make sure to read the documentation +# available at http://redis.io web site. + +########################## CLUSTER DOCKER/NAT support ######################## + +# In certain deployments, Redis Cluster nodes address discovery fails, because +# addresses are NAT-ted or because ports are forwarded (the typical case is +# Docker and other containers). +# +# In order to make Redis Cluster working in such environments, a static +# configuration where each node knows its public address is needed. The +# following two options are used for this scope, and are: +# +# * cluster-announce-ip +# * cluster-announce-port +# * cluster-announce-bus-port +# +# Each instructs the node about its address, client port, and cluster message +# bus port. The information is then published in the header of the bus packets +# so that other nodes will be able to correctly map the address of the node +# publishing the information. +# +# If the above options are not used, the normal Redis Cluster auto-detection +# will be used instead. +# +# Note that when remapped, the bus port may not be at the fixed offset of +# clients port + 10000, so you can specify any port and bus-port depending +# on how they get remapped. If the bus-port is not set, a fixed offset of +# 10000 will be used as usual. +# +# Example: +# +# cluster-announce-ip 10.1.1.5 +# cluster-announce-port 6379 +# cluster-announce-bus-port 6380 + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +################################ LATENCY MONITOR ############################## + +# The Redis latency monitoring subsystem samples different operations +# at runtime in order to collect data related to possible sources of +# latency of a Redis instance. +# +# Via the LATENCY command this information is available to the user that can +# print graphs and obtain reports. +# +# The system only logs operations that were performed in a time equal or +# greater than the amount of milliseconds specified via the +# latency-monitor-threshold configuration directive. When its value is set +# to zero, the latency monitor is turned off. +# +# By default latency monitoring is disabled since it is mostly not needed +# if you don't have latency issues, and collecting data has a performance +# impact, that while very small, can be measured under big load. Latency +# monitoring can easily be enabled at runtime using the command +# "CONFIG SET latency-monitor-threshold " if needed. +latency-monitor-threshold 0 + +############################# EVENT NOTIFICATION ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at http://redis.io/topics/notifications +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# It is possible to select the events that Redis will notify among a set +# of classes. Every class is identified by a single character: +# +# K Keyspace events, published with __keyspace@__ prefix. +# E Keyevent events, published with __keyevent@__ prefix. +# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... +# $ String commands +# l List commands +# s Set commands +# h Hash commands +# z Sorted set commands +# x Expired events (events generated every time a key expires) +# e Evicted events (events generated when a key is evicted for maxmemory) +# t Stream commands +# m Key-miss events (Note: It is not included in the 'A' class) +# A Alias for g$lshzxet, so that the "AKE" string means all the events +# (Except key-miss events which are excluded from 'A' due to their +# unique nature). +# +# The "notify-keyspace-events" takes as argument a string that is composed +# of zero or multiple characters. The empty string means that notifications +# are disabled. +# +# Example: to enable list and generic events, from the point of view of the +# event name, use: +# +# notify-keyspace-events Elg +# +# Example 2: to get the stream of the expired keys subscribing to channel +# name __keyevent@0__:expired use: +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +############################### GOPHER SERVER ################################# + +# Redis contains an implementation of the Gopher protocol, as specified in +# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt). +# +# The Gopher protocol was very popular in the late '90s. It is an alternative +# to the web, and the implementation both server and client side is so simple +# that the Redis server has just 100 lines of code in order to implement this +# support. +# +# What do you do with Gopher nowadays? Well Gopher never *really* died, and +# lately there is a movement in order for the Gopher more hierarchical content +# composed of just plain text documents to be resurrected. Some want a simpler +# internet, others believe that the mainstream internet became too much +# controlled, and it's cool to create an alternative space for people that +# want a bit of fresh air. +# +# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol +# as a gift. +# +# --- HOW IT WORKS? --- +# +# The Redis Gopher support uses the inline protocol of Redis, and specifically +# two kind of inline requests that were anyway illegal: an empty request +# or any request that starts with "/" (there are no Redis commands starting +# with such a slash). Normal RESP2/RESP3 requests are completely out of the +# path of the Gopher protocol implementation and are served as usual as well. +# +# If you open a connection to Redis when Gopher is enabled and send it +# a string like "/foo", if there is a key named "/foo" it is served via the +# Gopher protocol. +# +# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher +# talking), you likely need a script like the following: +# +# https://github.com/antirez/gopher2redis +# +# --- SECURITY WARNING --- +# +# If you plan to put Redis on the internet in a publicly accessible address +# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance. +# Once a password is set: +# +# 1. The Gopher server (when enabled, not by default) will still serve +# content via Gopher. +# 2. However other commands cannot be called before the client will +# authenticate. +# +# So use the 'requirepass' option to protect your instance. +# +# Note that Gopher is not currently supported when 'io-threads-do-reads' +# is enabled. +# +# To enable Gopher support, uncomment the following line and set the option +# from no (the default) to yes. +# +# gopher-enabled no + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +# Lists are also encoded in a special way to save a lot of space. +# The number of entries allowed per internal list node can be specified +# as a fixed maximum size or a maximum number of elements. +# For a fixed maximum size, use -5 through -1, meaning: +# -5: max size: 64 Kb <-- not recommended for normal workloads +# -4: max size: 32 Kb <-- not recommended +# -3: max size: 16 Kb <-- probably not recommended +# -2: max size: 8 Kb <-- good +# -1: max size: 4 Kb <-- good +# Positive numbers mean store up to _exactly_ that number of elements +# per list node. +# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), +# but if your use case is unique, adjust the settings as necessary. +list-max-ziplist-size -2 + +# Lists may also be compressed. +# Compress depth is the number of quicklist ziplist nodes from *each* side of +# the list to *exclude* from compression. The head and tail of the list +# are always uncompressed for fast push/pop operations. Settings are: +# 0: disable all list compression +# 1: depth 1 means "don't start compressing until after 1 node into the list, +# going from either the head or tail" +# So: [head]->node->node->...->node->[tail] +# [head], [tail] will always be uncompressed; inner nodes will compress. +# 2: [head]->[next]->node->node->...->node->[prev]->[tail] +# 2 here means: don't compress head or head->next or tail->prev or tail, +# but compress all nodes between them. +# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] +# etc. +list-compress-depth 0 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happen to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When an HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Streams macro node max size / items. The stream data structure is a radix +# tree of big nodes that encode multiple items inside. Using this configuration +# it is possible to configure how big a single node can be in bytes, and the +# maximum number of items it may contain before switching to a new node when +# appending new stream entries. If any of the following settings are set to +# zero, the limit is ignored, so for instance it is possible to set just a +# max entires limit by setting max-bytes to 0 and max-entries to the desired +# value. +stream-node-max-bytes 4096 +stream-node-max-entries 100 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# actively rehash the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply from time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients including MONITOR clients +# replica -> replica clients +# pubsub -> clients subscribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and replica clients, since +# subscribers and replicas receive data in a push fashion. +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Client query buffers accumulate new commands. They are limited to a fixed +# amount by default in order to avoid that a protocol desynchronization (for +# instance due to a bug in the client) will lead to unbound memory usage in +# the query buffer. However you can configure it here if you have very special +# needs, such us huge multi/exec requests or alike. +# +# client-query-buffer-limit 1gb + +# In the Redis protocol, bulk requests, that are, elements representing single +# strings, are normally limited to 512 mb. However you can change this limit +# here, but must be 1mb or greater +# +# proto-max-bulk-len 512mb + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform according to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 10 + +# Normally it is useful to have an HZ value which is proportional to the +# number of clients connected. This is useful in order, for instance, to +# avoid too many clients are processed for each background task invocation +# in order to avoid latency spikes. +# +# Since the default HZ value by default is conservatively set to 10, Redis +# offers, and enables by default, the ability to use an adaptive HZ value +# which will temporarily raise when there are many connected clients. +# +# When dynamic HZ is enabled, the actual configured HZ will be used +# as a baseline, but multiples of the configured HZ value will be actually +# used as needed once more clients are connected. In this way an idle +# instance will use very little CPU time while a busy instance will be +# more responsive. +dynamic-hz yes + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 32 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + +# When redis saves RDB file, if the following option is enabled +# the file will be fsync-ed every 32 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +rdb-save-incremental-fsync yes + +# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good +# idea to start with the default settings and only change them after investigating +# how to improve the performances and how the keys LFU change over time, which +# is possible to inspect via the OBJECT FREQ command. +# +# There are two tunable parameters in the Redis LFU implementation: the +# counter logarithm factor and the counter decay time. It is important to +# understand what the two parameters mean before changing them. +# +# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis +# uses a probabilistic increment with logarithmic behavior. Given the value +# of the old counter, when a key is accessed, the counter is incremented in +# this way: +# +# 1. A random number R between 0 and 1 is extracted. +# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). +# 3. The counter is incremented only if R < P. +# +# The default lfu-log-factor is 10. This is a table of how the frequency +# counter changes with a different number of accesses with different +# logarithmic factors: +# +# +--------+------------+------------+------------+------------+------------+ +# | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | +# +--------+------------+------------+------------+------------+------------+ +# | 0 | 104 | 255 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 1 | 18 | 49 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 10 | 10 | 18 | 142 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 100 | 8 | 11 | 49 | 143 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# +# NOTE: The above table was obtained by running the following commands: +# +# redis-benchmark -n 1000000 incr foo +# redis-cli object freq foo +# +# NOTE 2: The counter initial value is 5 in order to give new objects a chance +# to accumulate hits. +# +# The counter decay time is the time, in minutes, that must elapse in order +# for the key counter to be divided by two (or decremented if it has a value +# less <= 10). +# +# The default value for the lfu-decay-time is 1. A special value of 0 means to +# decay the counter every time it happens to be scanned. +# +# lfu-log-factor 10 +# lfu-decay-time 1 + +########################### ACTIVE DEFRAGMENTATION ####################### +# +# What is active defragmentation? +# ------------------------------- +# +# Active (online) defragmentation allows a Redis server to compact the +# spaces left between small allocations and deallocations of data in memory, +# thus allowing to reclaim back memory. +# +# Fragmentation is a natural process that happens with every allocator (but +# less so with Jemalloc, fortunately) and certain workloads. Normally a server +# restart is needed in order to lower the fragmentation, or at least to flush +# away all the data and create it again. However thanks to this feature +# implemented by Oran Agra for Redis 4.0 this process can happen at runtime +# in a "hot" way, while the server is running. +# +# Basically when the fragmentation is over a certain level (see the +# configuration options below) Redis will start to create new copies of the +# values in contiguous memory regions by exploiting certain specific Jemalloc +# features (in order to understand if an allocation is causing fragmentation +# and to allocate it in a better place), and at the same time, will release the +# old copies of the data. This process, repeated incrementally for all the keys +# will cause the fragmentation to drop back to normal values. +# +# Important things to understand: +# +# 1. This feature is disabled by default, and only works if you compiled Redis +# to use the copy of Jemalloc we ship with the source code of Redis. +# This is the default with Linux builds. +# +# 2. You never need to enable this feature if you don't have fragmentation +# issues. +# +# 3. Once you experience fragmentation, you can enable this feature when +# needed with the command "CONFIG SET activedefrag yes". +# +# The configuration parameters are able to fine tune the behavior of the +# defragmentation process. If you are not sure about what they mean it is +# a good idea to leave the defaults untouched. + +# Enabled active defragmentation +# activedefrag no + +# Minimum amount of fragmentation waste to start active defrag +# active-defrag-ignore-bytes 100mb + +# Minimum percentage of fragmentation to start active defrag +# active-defrag-threshold-lower 10 + +# Maximum percentage of fragmentation at which we use maximum effort +# active-defrag-threshold-upper 100 + +# Minimal effort for defrag in CPU percentage, to be used when the lower +# threshold is reached +# active-defrag-cycle-min 1 + +# Maximal effort for defrag in CPU percentage, to be used when the upper +# threshold is reached +# active-defrag-cycle-max 25 + +# Maximum number of set/hash/zset/list fields that will be processed from +# the main dictionary scan +# active-defrag-max-scan-fields 1000 + +# Jemalloc background thread for purging will be enabled by default +jemalloc-bg-thread yes + +# It is possible to pin different threads and processes of Redis to specific +# CPUs in your system, in order to maximize the performances of the server. +# This is useful both in order to pin different Redis threads in different +# CPUs, but also in order to make sure that multiple Redis instances running +# in the same host will be pinned to different CPUs. +# +# Normally you can do this using the "taskset" command, however it is also +# possible to this via Redis configuration directly, both in Linux and FreeBSD. +# +# You can pin the server/IO threads, bio threads, aof rewrite child process, and +# the bgsave child process. The syntax to specify the cpu list is the same as +# the taskset command: +# +# Set redis server/io threads to cpu affinity 0,2,4,6: +# server_cpulist 0-7:2 +# +# Set bio threads to cpu affinity 1,3: +# bio_cpulist 1,3 +# +# Set aof rewrite child process to cpu affinity 8,9,10,11: +# aof_rewrite_cpulist 8-11 +# +# Set bgsave child process to cpu affinity 1,10,11 +# bgsave_cpulist 1,10-11 + +# In some cases redis will emit warnings and even refuse to start if it detects +# that the system is in bad state, it is possible to suppress these warnings +# by setting the following config which takes a space delimited list of warnings +# to suppress +# +# ignore-warnings ARM64-COW-BUG diff --git a/tests/playwright/rte/oss-sentinel/Dockerfile b/tests/playwright/rte/oss-sentinel/Dockerfile new file mode 100644 index 0000000000..61e4b8b533 --- /dev/null +++ b/tests/playwright/rte/oss-sentinel/Dockerfile @@ -0,0 +1,14 @@ +FROM redis:5 + + +ADD sentinel.conf /etc/redis/sentinel.conf +RUN chown redis:redis /etc/redis/sentinel.conf +ENV SENTINEL_QUORUM 2 +ENV SENTINEL_DOWN_AFTER 5000 +ENV SENTINEL_FAILOVER 10000 +ENV SENTINEL_PORT 26000 +ENV AUTH_PASS password +ENV REQUIREPASS="" +ADD entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/entrypoint.sh +ENTRYPOINT ["entrypoint.sh"] \ No newline at end of file diff --git a/tests/playwright/rte/oss-sentinel/entrypoint.sh b/tests/playwright/rte/oss-sentinel/entrypoint.sh new file mode 100644 index 0000000000..390a969f1b --- /dev/null +++ b/tests/playwright/rte/oss-sentinel/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +sed -i "s/\$SENTINEL_PORT/$SENTINEL_PORT/g" /etc/redis/sentinel.conf +sed -i "s/\$SENTINEL_QUORUM/$SENTINEL_QUORUM/g" /etc/redis/sentinel.conf +sed -i "s/\$SENTINEL_DOWN_AFTER/$SENTINEL_DOWN_AFTER/g" /etc/redis/sentinel.conf +sed -i "s/\$SENTINEL_FAILOVER/$SENTINEL_FAILOVER/g" /etc/redis/sentinel.conf +sed -i "s/\$AUTH_PASS/$AUTH_PASS/g" /etc/redis/sentinel.conf +sed -i "s/\$REQUIREPASS/$REQUIREPASS/g" /etc/redis/sentinel.conf + +exec docker-entrypoint.sh redis-server /etc/redis/sentinel.conf --sentinel diff --git a/tests/playwright/rte/oss-sentinel/sentinel.conf b/tests/playwright/rte/oss-sentinel/sentinel.conf new file mode 100644 index 0000000000..0eca71c59a --- /dev/null +++ b/tests/playwright/rte/oss-sentinel/sentinel.conf @@ -0,0 +1,15 @@ +port 26379 +dir /tmp +sentinel monitor primary-group-1 oss-sentinel-primary-1 6379 $SENTINEL_QUORUM +sentinel down-after-milliseconds primary-group-1 $SENTINEL_DOWN_AFTER +sentinel parallel-syncs primary-group-1 1 +sentinel failover-timeout primary-group-1 $SENTINEL_FAILOVER +sentinel auth-pass primary-group-1 password + +sentinel monitor primary-group-2 oss-sentinel-primary-2 6379 $SENTINEL_QUORUM +sentinel down-after-milliseconds primary-group-2 $SENTINEL_DOWN_AFTER +sentinel parallel-syncs primary-group-2 1 +sentinel failover-timeout primary-group-2 $SENTINEL_FAILOVER +sentinel auth-pass primary-group-2 password + +requirepass "password" diff --git a/tests/playwright/rte/oss-standalone-big/Dockerfile b/tests/playwright/rte/oss-standalone-big/Dockerfile new file mode 100644 index 0000000000..66b7369068 --- /dev/null +++ b/tests/playwright/rte/oss-standalone-big/Dockerfile @@ -0,0 +1,10 @@ +FROM redislabs/redismod + +ARG TEST_DB_DUMP +ADD $TEST_DB_DUMP /data/ + +ADD entrypoint.sh . +RUN chmod +x entrypoint.sh + +ENTRYPOINT ["sh", "entrypoint.sh", "redis-server"] +CMD ["--loadmodule", "/usr/lib/redis/modules/redisai.so", "--loadmodule", "/usr/lib/redis/modules/redisearch.so", "--loadmodule", "/usr/lib/redis/modules/redisgraph.so", "--loadmodule", "/usr/lib/redis/modules/redistimeseries.so", "--loadmodule", "/usr/lib/redis/modules/rejson.so", "--loadmodule", "/usr/lib/redis/modules/redisbloom.so", "--loadmodule", "/usr/lib/redis/modules/redisgears.so", "Plugin", "/var/opt/redislabs/modules/rg/plugin/gears_python.so"] diff --git a/tests/playwright/rte/oss-standalone-big/entrypoint.sh b/tests/playwright/rte/oss-standalone-big/entrypoint.sh new file mode 100644 index 0000000000..de1dcd8155 --- /dev/null +++ b/tests/playwright/rte/oss-standalone-big/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ -e dump.tar.gz ] +then + echo 'Extracting .rdb file...' + tar -zxvf dump.tar.gz +fi + +exec "$@" diff --git a/tests/playwright/rte/oss-standalone-tls/Dockerfile b/tests/playwright/rte/oss-standalone-tls/Dockerfile new file mode 100644 index 0000000000..0cf6ec834f --- /dev/null +++ b/tests/playwright/rte/oss-standalone-tls/Dockerfile @@ -0,0 +1,13 @@ +FROM bitnami/redis:6.0.8 + +ENV ALLOW_EMPTY_PASSWORD yes + +# TLS options +ENV REDIS_TLS_ENABLED yes +ENV REDIS_TLS_PORT 6379 +ENV REDIS_TLS_CERT_FILE /opt/bitnami/redis/certs/redis.crt +ENV REDIS_TLS_KEY_FILE /opt/bitnami/redis/certs/redis.key +ENV REDIS_TLS_CA_FILE /opt/bitnami/redis/certs/redisCA.crt +ENV REDIS_TLS_AUTH_CLIENTS yes + +COPY --chown=1001 ./certs /opt/bitnami/redis/certs/ diff --git a/tests/playwright/rte/oss-standalone-tls/certs/redis.crt b/tests/playwright/rte/oss-standalone-tls/certs/redis.crt new file mode 100644 index 0000000000..2761116425 --- /dev/null +++ b/tests/playwright/rte/oss-standalone-tls/certs/redis.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEzTCCArWgAwIBAgIUALiX/81ndGTG8UCPzu8r4Ev2IhEwDQYJKoZIhvcNAQEL +BQAwHzELMAkGA1UEBhMCQVUxEDAOBgNVBAMMB2V4YW1wbGUwHhcNMjExMDI4MTMy +NzI3WhcNMzExMDI2MTMyNzI3WjANMQswCQYDVQQGEwJBVTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAM4osMW/hBlde+/E20wP9X+zJ0AMD6OtfhqQ5brC +gbVs9mPccZ/8R0fj83YtZfnoEodMZ/7yUTeCCPMdprAezMU1KBf9EpZmTdWhpO3e +kESHcQdsKkqGtyjYF7dDahTmKt4a4aHlPH0DJLltB5HlbVabkzlo+3S8QaNwH5lY +yJTQIqiqVzs9oRLT76nZuJjsym0dNXE42rO3KCniI6kvJDmUzBD8Wc94iDExfy7q +qHyV7b2DCp1w7XP4yrQAFQ6kiVqNcfTTAO4MHNP54V2nZLPdOsUD5BsYY8hu0HDc +/PisZ9ZMcw7LMfpUd3dfA9zefQXPQsWJK20ZCNmdIFtwvIFUpYu/FEF3FWO83zeI +XkVZiuCOnoFvp8JIDvXgzXUBWzvYmxLqVSZAuabqU5pKRswDPLGlZTkHbuP2DiXD +LD5AsTnICpzYkUeERSZKf2qc/nTUk04W/7FUT/75ItVzZvu90mPJlmArB0j4zdAG +KwKo8v/cF1hA1YznhibxcUAA/Q/O3Y6CPQ7C3NeaGKcycgUxWoEY3Leno40ukijd +R0MvsaY7V0/up37fkPtH9rcCkZOGVT5Q4Ww9gVO3yseXVkxbJyzHV1tuwg6yY9wO +LOU2Bbmazkjlkb8a5OyQol2zZNJ0L3lvRWTildtGUTsBkeqI6HAedTTHJkhjHh/P +P0+TAgMBAAGjEzARMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQELBQADggIB +AKn+aH60zdd4ItMOhgd4BIok/u6S4NF0oe4kDwC/EU5C0hbuT6gLAn7ArHMeadue +OuSnhqIPfxNR9WCes9OU1O00WeCuWCE4XfNMMFN5iDFfLxO4Oy5Adt0T74hWbcy6 +h28TdcjrkJEr7HR59G5tQ8TW5gVB4a0WXDw0ob9DSxbFKZU1uZm9L/+MgB/SNCHL +GZSKt75Z/M10b9BTC3OG9swsoWvXEjR2ICiwzk+LxVf5K38faDyBrNJVglrpEUZz +gP60kL73qK0y1/i35UuP0yIJIy48XnDsSByN7eBVsNTGMW3CFLKWA4RVfnEHNUff +vsLHXZFYsUIPnPc5jksFwb/wKAe9JbCrgQPhBYaIYkRGiYt64C48r3boIIVoz9+1 +9Nq0Ik06fCzlI9APq2nzEiVeB7mDyZ692neu32QM6zRkYor+W8uI21YnRJWlOx7+ +x2GIh2EZnEYNvbpbvk/fV5AqkYOu9auRAkcKfME7dJ3Gwndl0YBOjE2DMTv6vIjS +dVuGXQCvlzkRAnPMh5MR5/bSUKVvBryXs9ecAMgoVXBVB+4tGWct5ziL+8qyNtgA +WJ2EWj3xtLlMwwQmLjRsCrZjL4liLJG8Yn8Ehfq1rRJREH2O8uYKCO1fdhuI0Y5S +iBPfqJi6QBHj7i01K9OpNUB7l+xAFLA3cBsegcm2GPoL +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/redis.key b/tests/playwright/rte/oss-standalone-tls/certs/redis.key new file mode 100644 index 0000000000..fb0777e3ea --- /dev/null +++ b/tests/playwright/rte/oss-standalone-tls/certs/redis.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDOKLDFv4QZXXvv +xNtMD/V/sydADA+jrX4akOW6woG1bPZj3HGf/EdH4/N2LWX56BKHTGf+8lE3ggjz +HaawHszFNSgX/RKWZk3VoaTt3pBEh3EHbCpKhrco2Be3Q2oU5ireGuGh5Tx9AyS5 +bQeR5W1Wm5M5aPt0vEGjcB+ZWMiU0CKoqlc7PaES0++p2biY7MptHTVxONqztygp +4iOpLyQ5lMwQ/FnPeIgxMX8u6qh8le29gwqdcO1z+Mq0ABUOpIlajXH00wDuDBzT ++eFdp2Sz3TrFA+QbGGPIbtBw3Pz4rGfWTHMOyzH6VHd3XwPc3n0Fz0LFiSttGQjZ +nSBbcLyBVKWLvxRBdxVjvN83iF5FWYrgjp6Bb6fCSA714M11AVs72JsS6lUmQLmm +6lOaSkbMAzyxpWU5B27j9g4lwyw+QLE5yAqc2JFHhEUmSn9qnP501JNOFv+xVE/+ ++SLVc2b7vdJjyZZgKwdI+M3QBisCqPL/3BdYQNWM54Ym8XFAAP0Pzt2Ogj0OwtzX +mhinMnIFMVqBGNy3p6ONLpIo3UdDL7GmO1dP7qd+35D7R/a3ApGThlU+UOFsPYFT +t8rHl1ZMWycsx1dbbsIOsmPcDizlNgW5ms5I5ZG/GuTskKJds2TSdC95b0Vk4pXb +RlE7AZHqiOhwHnU0xyZIYx4fzz9PkwIDAQABAoICAHyc/+0oDHNAnK+bsGrTorNj +2S/Pmox3TChGuXYgKEM/79cA4vWvim6cDQe7/U4Hx1tdBeeHFSyWP06k96Kxm1kA +/pExedDLWfTt1kGqLE4gCGRSL2YI9CGOLRerei3TysmiOgygAeYWxlYG33KC2Ypm +U6F6IbS4LnzaQ19v2R6KiMim3j+CyyAUV2O1pO1bBCjcZPdhRGEpLu/SL3gOdLkR +hiAmSSstUjVaE+SKFvnnrmLFGN996SoWkoAnJJNLRXMk2GMCQCejzrEa8+ymSCqo +aOO5rGHsZjQ7N2dhTNALdmCEqW+hxz3nXKcdGbqiCbQ/Sb8ZYNR7M2xGm85p4Ka9 +0UK4cOM1VJPwz8hotSKAUmXnpbu73CsZi5HyzwZkk4FpcaYCYrCbGVmm1cIKEKI7 +8SN/oqgFdj4Ha9cemnu+RecQZouK+wPWtcILd2kstJl52TV952fVOrnXQDo6XCXB +fbs9IYN1hB6N79xv4L7Jj53hSRMeSNf70Ejkh1FXPOvmvFT12wy5JQdBBR5nnb4a +GEsMpGVe1k3bxjK7K263tLSH0UZ8dMgdSx1E4D1hT1K/gEwTSMOJ0E1R0M6SJmF2 +6TnZ0MbJWx6PbICmyZrd2agfTQrq6CgY1fWLGbQrtnwXtsUR7PiHarydXfs3V8g1 +xHnK1bItOBBMOMcWV93hAoIBAQD2xMceUfBUN0TeWrJ4izD3lUYxmWqjTg7kPcjJ +0VN8v3txGAcyPmbuz5AEzvdFjTispoZNB9QOrmsUVWTQDE5otnei9kHWzqJWYHg4 +USuUuAh8OJGCiepo8zHT3qHDNhKGtOAp5LC8TaOznUFr35zCBCOsvQfRUKrv5IOc +vCFjO07Xly8+M3qK7/UswRQ6480VlE2t1p+VNaORHdTDg2tes3/9owuiNmR/sPT8 +nIoe01LS7qmZoiB1vracaLcBf1Iwd7RvKg7mgFJzmowZUYxyX2YGK5qZ1h74In2X +55+qQnNW0RwPijopTv711pMhMKWl8i3ilcCfoeBXz8zCwFfbAoIBAQDV3wHAO7ic +MYP/Bm5jgIwlix1eOWY/yB+VqdYn2GmC49vTEIlIVlFRq0vZE06iUxs87BIV08zO +4w/iKXd7ktkuhphiEuU2yXA3LQPHpbSOW43RONbf4glFU/DlP/P6fiybbWj6+f7L +7Zbvtz5AW03Y4ZpagJTqOgVdJ0MdLnh9vZj6okGGV1fidKtG6hr7Z/oLhnl9aAZK +4vrvBZ//qz99vEVByiV5kRaJDulu+diBy4n6iBjzjHA5a9e7lY3sUBw3DMgb7kYs +JJPkCPdSxCYq4Ef3z/Eao0tyUuCzyznfCMGJA1gBdTpwDNDCTaXqkjR5nvsdE5k0 +IVQgFPtcOPCpAoIBABujNlni+3OzLPdqWQq/LCDOiyoK8LKRj4FomhBgbWVPXNfx +xPyPmJ+uh4bCV1dm1a4giHIgKlPqnPuOBNh4SF/Z79REmGMiiXP7IfvMu4DQi8K9 +4y4nnCVc93uvN5bRe4mywFhw0IqGd4sqVaVrSfdA124FTdbXng14HnVzbJncjpv+ +xr/ErDjbXy5AAbAGy3VbQsfxfbYMZ+Fc4fNzyJa2q+MQW8EzLlZOz2Frdty09lXB +fSVDzzbgwTsLT1PPmrjq7z50C28teA6ShJZhV8WHgbm3MH2CSb2ov0BAJNXA04Ip +sWbcKF9wBYYrHhddh2/qi9EQzJ4UVzf+ggRd3nkCggEAWcjyWjp4KRJcgJ65jwoz +S7uYS6s7MsGYCOOw5S9kNC/mZDhH+ddK8kdAY1RIqbrL74qHmSQ+kgge7epMn9Mp +W+/jXyDhm1t7wZ4jPRhisXTcF56ODpU9IR65PfTYPyvjHCkVbm+vOPt4ZxB9kNUD +3G3xt9bNLXvILrBB66lLqjYDWAzwBy751Tb3hKDZTPv8rAP7Uttt8NhTUi8BWXsR +/34fcRwlGWEAne9lrlIzQ2IofcXO+8fUgTa17ak+WJvVDINQKvGgAf4lHBFrixKP +l2ZqsC1a4bz1+nuym6hQlkJ9xUBjHNGTA+FNbpTcd5qDbx9/+lf09D6dq45DbBb3 +aQKCAQBrnFYocTm/fIeKo1n1kyF2ULkd6k984ztH8UyluXleSS1ShFFoo/x3vz35 +fsZNUggRnSe7/OBGZYquF/1roVULI1hKh4tbEmW4SWNeTFvwXKdRe6T7NnWSZtS/ +KtamA3lT2wtoEVOvfMo8M0hoFuRWdT2M0i+LKZQdRsq18XPLqdHt1kkSNcnPDERm +4gLQ8zXTf2fHrtZmyM8fc0GuTVwprPFeJkLtSPehkeXSTgb6rpyelX9NBUILwRgP +nw0+cbjFDFKaLnIrMFoVAAn/8DcnbbSt1TZhgNsMxY+GHWPBYW8SUi5nBmQQtmA7 +n3ju44acIPvJ9sWuZruVlWZGFaHm +-----END PRIVATE KEY----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt b/tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt new file mode 100644 index 0000000000..796fcb3e05 --- /dev/null +++ b/tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFHzCCAwegAwIBAgIUKeAfHPO6uJBW+s8fY2cWKOc+DfgwDQYJKoZIhvcNAQEL +BQAwHzELMAkGA1UEBhMCQVUxEDAOBgNVBAMMB2V4YW1wbGUwHhcNMjExMDI4MTMy +NzI2WhcNMzExMDI2MTMyNzI2WjAfMQswCQYDVQQGEwJBVTEQMA4GA1UEAwwHZXhh +bXBsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANF9A4aeHru2fX1j +U+Bz9D2supYsMG64f+wXNrFPTMxPS/rdjNcqAWeVCknY7d8EO0uBf64Gm4ufQAPV +boINIdgoso9tGfl5LMaaiYq0aD5CK0wmU38pPbKA2Vr9bkrNIYLUFU6oPI7RJ5fL +Pl/vbvHyaXQKcDd5xxusAu3Ytrylq3WaLNWwhT//WRor4SU2qt9s06PiOgCABY+D +olMXI72gDaehRhnbOVXc6GadlHCsE5GHYJ3WcLLY0rGEdlwphcEG5TRVHGBiHOg/ +J0vsiuhwTLyRqQq5L6eFm33d4aRI9JLY8LlU5ywGiVoNl+fFdQr3ovWw7eObQSbg +BuOJhQBTpEmiPgiOC3kAUUrgT/uGS1x9RX+Wj0sY6zs+qOkfhFAcScXQBeZSLNT9 +RYAjZQOTtTQYVwH8NcF2MlwI3tb3qk2+2Xby4YfTHxp42B8IHkedwfFzrwfUDnNM +Cm3GSVtDGv/j4/7fp0oZZROpd5+h1wRhR/HO08rkpwuobo6xGRrrxqbdlsid3OB4 +Kk92Wl8reccxyr2a/7OlrWk284xpQI/nlU6a8bByJp2eTPYuNJFfJkrqua94YOJy +K4d4tLNbQ4X/5g12unGEHg8/HVNHJjCKiU2Gwxhxm50EqmgdgaboDmf+GuVF0tL1 +kGPbbjSrlt2pS+Tdza9EJfmAzVppAgMBAAGjUzBRMB0GA1UdDgQWBBQWqqhnKa+s +5ToC0TYKlIdfytda/jAfBgNVHSMEGDAWgBQWqqhnKa+s5ToC0TYKlIdfytda/jAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQATnfNftaIalbJmNHrq +7KIxyAAEimiIvci5i1f2X8ZB4XY1TWcf0eQ/+oJW9E2Kj83yGaAqKg1FFP8ugquG +pxzMyljTlQi3cWp58ByaYg2XMEyRY5FtbcdbHHJgdM2PSHsegWN/vfJw3H3J2673 +J6Kc/69IwJcE+aYDbLa/cnnRBn9AKHdtNoRmPH+s162oo8I86LQuG942Hx2CHCjt +ttkLwVBtsBKc5hRaPR+Psx68jS41iHJGHUwHNcPl7llBAQe8kKNg4GHJWT5vh7rd +rw4jAGCsoaE5Ol1HyRDprpdcC4o+eQbhMrjMcFzYduuCx1F96BlSXA2mQe+9lD08 +LzdS35ILmSqCTbtOcdHCmnjWp9fhl5mIrJ+I3G33QPHaHJXBfWGNidxjkibwdVxK +eNAOv4lEMCoVQ0occzNyUluJQUFJyvXtXWFSErRH6b78Gsc/AvPijPbSNuT8hRK9 +GC3yRYltDFXcr4+2lxJyoQoR6Y8399oaJm4U17fOIwlM+iI8dT8x+qsT8brw+5kk +oKu4jz8jfkZrUF8V8hIfAUc08IvMAmDwvIMeAjXFmDBEECxXBGRw2hTcY/53Nfdt +PRWzaL/YtKOy/9UfJQKs3Ihqte59/v3EJO9x9vTLmcpoCgh9piNVgD6OS38cOiEa +snS90+qMig9Gx3aJ+UvktWcp3Q== +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/user.crt b/tests/playwright/rte/oss-standalone-tls/certs/user.crt new file mode 100644 index 0000000000..ecd9b6f068 --- /dev/null +++ b/tests/playwright/rte/oss-standalone-tls/certs/user.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEzTCCArWgAwIBAgIUALiX/81ndGTG8UCPzu8r4Ev2IhIwDQYJKoZIhvcNAQEL +BQAwHzELMAkGA1UEBhMCQVUxEDAOBgNVBAMMB2V4YW1wbGUwHhcNMjExMDI4MTQz +NTAzWhcNMzExMDI2MTQzNTAzWjANMQswCQYDVQQGEwJBVTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKOod8jpFXqjtNvl0FgIkg0fSZbzvh7jbI7TEUVQ +myeZxjmB3fZh5f6dxM7TZ048CUOeUeq3lemDqay+Moku0rL4PsFNe8z1C1zHuhf9 +4Qw/f7rMBIZ73L4Y/7cPWfjZbeme06+D7HMBZGTWGHZCWrqZQOwA3hKBjC3VY/a5 +z6oP78+w18WDpnavGwXwgCd1yTOwz3tVJUOcJdjGv3iwrHABcGVfxUEKTabP+p6V +HA/+w4AlCloS57GQCh0RWCXMyfekv6MGBaqQa6GtOK5ScLJ1YSlJ6PRoK2N+shbw +L/kQGlilgYBVGOQgNKd94+PwJgOCy72S7p9yF3ZTBB4/51Bwl7IV74Om/GmqzJMx +xY9/PPaxKlOkP+dW41/IrcDULdh0jAfe9rKdFf9/9NWA37S68pKFpzRuRrpLqIwm +BPtHvtLnTbhgmS/O1Rwmxqs8r+VA6D8+/drAor/KAcCwgRiYLvhvl4ABoqj4toEK +jCXAR/jeoLAb8HDBzkot4hhJPjMhQMYX9/HfdK4YX359EkHdsO/+R6+ImXb68DS5 +zh0028ktMM+KEhWSffSmU3imZOrH1/TQfSxfzuTHvyd0HXAHvzx+w1VWNK4fqU8O +tDbMt1GAaatrfrqwP4qTjzLEqtlJLIjg4qgzpYCRUvgVdxyeii9o7IeYT8I6Penf +QpAJAgMBAAGjEzARMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQELBQADggIB +ABb+A9C1AqstP5d2HXS/pIef1BNYAV8A/D2+nUpEXiautmjRZBRNwzBX2ffEZWV7 +EMkvqrRwz2ZgOJ4BRzJiDIYoF8dOhd0lc/0PqoR0xVzjOFDqd0ZcPHAjaY3UoBE7 +jQSQ6ccc1tY5peNLAWCvRO6V9yhdV/SKGhveXGl/24MK9juwArnAitekCWZJQifT +CFOJX5UvifrT8s0v0AqkycaNpkMvl0BAl4DRDJ3+EwZmzfOdATawyXBVXHt1Gz+N +iskPJAJsIjEdFYTjDUzwRN3bHFbTRXt2v1U18YIvMjvxq8MlITEC2lEW+3Xu90d3 +aE/N9mLNJCgmZ2CGywWoaJlUXix2LTo5kT5coVVx0HK0tg5EcBua05qM3xO9Rgxv +HkCnm/jMeN4oQ5o7h+q7UQja8mg1bjCzlt+RxqoA1snjglra/h5I8TTEhvSfxEy7 +h5Wiwne/TH/e8fN1IYRDvv602MNSZnAEPyG3Hc5xQOSGNpoKOZG7tpU+mRYIvlPe +JgA5WNZ83y25JqSxF3kQuk7vrLByzEByqV3j+jIAQiHu/qIXwXUpkoV3L6A18yx/ +TbpQasr/bRFZKe83WlNl2ASAVyubal8ocmA0ua24/RV0I0VOCEXiIkl+pZ6e5Qn4 +L6Tryy5NxaEpUAZ9yv3P75PfNVQ3+vGYi3BLuhZUf/Dd +-----END CERTIFICATE----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/user.key b/tests/playwright/rte/oss-standalone-tls/certs/user.key new file mode 100644 index 0000000000..f201473517 --- /dev/null +++ b/tests/playwright/rte/oss-standalone-tls/certs/user.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCjqHfI6RV6o7Tb +5dBYCJINH0mW874e42yO0xFFUJsnmcY5gd32YeX+ncTO02dOPAlDnlHqt5Xpg6ms +vjKJLtKy+D7BTXvM9Qtcx7oX/eEMP3+6zASGe9y+GP+3D1n42W3pntOvg+xzAWRk +1hh2Qlq6mUDsAN4SgYwt1WP2uc+qD+/PsNfFg6Z2rxsF8IAndckzsM97VSVDnCXY +xr94sKxwAXBlX8VBCk2mz/qelRwP/sOAJQpaEuexkAodEVglzMn3pL+jBgWqkGuh +rTiuUnCydWEpSej0aCtjfrIW8C/5EBpYpYGAVRjkIDSnfePj8CYDgsu9ku6fchd2 +UwQeP+dQcJeyFe+DpvxpqsyTMcWPfzz2sSpTpD/nVuNfyK3A1C3YdIwH3vaynRX/ +f/TVgN+0uvKShac0bka6S6iMJgT7R77S5024YJkvztUcJsarPK/lQOg/Pv3awKK/ +ygHAsIEYmC74b5eAAaKo+LaBCowlwEf43qCwG/Bwwc5KLeIYST4zIUDGF/fx33Su +GF9+fRJB3bDv/keviJl2+vA0uc4dNNvJLTDPihIVkn30plN4pmTqx9f00H0sX87k +x78ndB1wB788fsNVVjSuH6lPDrQ2zLdRgGmra366sD+Kk48yxKrZSSyI4OKoM6WA +kVL4FXccnoovaOyHmE/COj3p30KQCQIDAQABAoICADAiwPiq9dJYjD2RXrJF8w9B +AJgRoP3cznVDx3SnvLrtE8yeUfbB3LADH3vl2iC8r8zfqCBtVv6T5zgTyTFoQDi7 +o1mfvKYP/QORCz87QRIlKyB6GWqky8xt9eiV71SuPxHT0Vdyaf15j1nJTvCZm63+ +nYXMy4SN7fkdJoXPKTFP9q0TyqMhkbie0Efy8P6qOj+l5aDU7lzwdIFKE88fx9g5 +1CE9BfuXWDeUPJagLNzXhhEO0/iiTtt/Djp2e4LCtTTNlEAS6V+9kqq/FEjRnqwe +sjE+t/ILIZfmD+OHSdTr05P3OhvQ671Na69H69uDKuslcV+U8/KZ0CTRTgjHqvUZ +eLNC8BZfAk8IZx637/rSlqPmxyS/j35vdslebTbWV2KM7jXPqSb9YokdoJ6M0NZX +IYiMK2reVzjy2YvX1Nhp4Xn68il10XVS4P9tFxyNWdTclCbuSlTfgc27ercQMMgY +fe7/8+A/QhV8tdly8W3HwTmvkmmWRSTMziI+zQzZmYYlAWb33rQYfMoHs4tEf2u2 +Rf0Oso56X73sc3ncnOFm+s5iwTeUH6EgF3ephJX4nR3canmtpy40nbXUJ+tAuaAj +uo56KNlPxIHKf96o2LGXGTrgbH39f0MebWOq/7YjtCg6sUbwuyyG3afLTHHuss13 +5bTJ5gD3rsiGUWjfY3oBAoIBAQDRR/BnDw501Hky32/Vhlt7Ef1iplru+Fh0yQj7 +2DQ+U+L1Ir4Q67ESH8qDnjkyLP1a8BDNOIEEGp5dBb+OHb/rwdb+RZ7OCIzFCQ/d +WR7m0ucuPBQwytQb7iXa9w0umZwoeTXEGP8aGe+bSBIHv8/em26rkSx0A1rxr2/O +1ho8xxgBmOxL3NSCnv56JUu/W0vFq/7OfWQ19SOvFahp3TeqR1gkHe76teWv11Pj ++RdiIIdCOifWChZPEdgMZD4rl1cs9QQb+n+WkRt/mZgtTIRQIe+we+vIha7TW46X +6A1DjSxV4WUSXvv10heYYpZkKzpNG9YOhRB3bvyDkRy11XZ5AoIBAQDIMUETtoa9 +EFNY+uieZwJCTWrrB1njLLRZS3eCAKsVegHD0txLG8H5VMkyZQErRe52zR9QXWU/ +U80tIO5BTbP3ME2AbjJvMwuiEe1lBKlVnn2JSGjbtzUMa1QBvDRmBEZkr8OneMN6 +p2tX3L3Vw8Xm/97rjkAgo3gQkqyDf6VZ4xvH2Wo405yMywcoifMZXo/PN9fI5V8S +fi3XjHrHzaY4cucbdaezVb4Zd0xwl+c6Ifw6+VtmRyfCEHk8yvSkoKWqdxtD0p3a +3e8txYoI/YZltAICZ2vjZPv05Ts/VwWVzaxUArYiUH+k6J+6yCavKWesmeac0vLG +yN07gpRPPsIRAoIBADIp+UDqxf9REsAT+L2I2BK27DKiR3eyhZlwuruLRnKOLv+t +VTu/ExGSFzvXSERzrkMG+jAG1D4El2MaxqCtFtzO+Na4H2mpePydwHTBMPwJH6rg +ccKES7VqLx6+SyWZYmn9K9sWVseN4fYpn1DGNHBad3ueb7ZbO4hlEfrVLTLWUjXH +zxQcGcA5liv3FqIGozH9mTUrr0KTwPrtyRGfGgGx2jnGBwuHYEf26D/j7Cv0Ohew +0u2mO1S2pT/LI2/VderrzBFcyQpxO9MpIOXyymBe0hJOkeTdzlsRPivBTrSbeT4Y +qd5ucByrQEahkwTtq6rh+jw+vwSx0MtElEotoZkCggEAB8ujNRlOdd5E4JokpMZu +GBbbqvtGTMpY24FMzgsonlV57B4x5drW2taqXwP/36eBea7TIVYBs02YF8HIhVJ5 +R47h9bZU0G+0bEM2c1CTJ3pceRQQwT2JG0qyor6pa6+O7izJ+aOCOSx7yZgW7FQL +SMt96r5HUP4MltifTx+RWMa3NjkJId1boz/kr3dvt/UutGsARBpqcVXogxQ9U7p2 +Voxi43bZaOpV1LgIifngTysznzhGjt0Gd1Ac6HkevapjyReKQEHbU8KApc+jaGY2 +7Y7s5RsR4HD2PrsOa5D/7q1roHnajcuErO9CCQvyNa/vEZGMoV61hXgc5UxYah2P +gQKCAQEAkzISMmGPyQT7t6F/P2dFmrotAUU8gsEaWhrlkS0AuREXv1p14I1OnQhS +eWU7I9qSG4NfslRi5WUnowyawQKYibShtJ9/tOWMTaEELVTDtPAIu2y9kcquiG2j +o34vfpByz0w1vhmd/hwcPAvBFV+oaGN6lPz9Pv9MlNBLJoMhCPdr3aBJJuThT1Ka +JQ/RT0XfU7XXSC74x7JwoKB4bobVHdON09yielC6w9wq9anqD18nrz/4wBwWDhDE +KPxeXVpnIZfhukmWxkBY8NLAOFEenS3f6D4wzuOD25mPRSJQTngh7w9XkZYzDnOo +iwa43+YOKJx4Qh4SeXLBc/Udm1eMTA== +-----END PRIVATE KEY----- diff --git a/tests/playwright/rte/redis-enterprise/Dockerfile b/tests/playwright/rte/redis-enterprise/Dockerfile new file mode 100644 index 0000000000..8af7097d05 --- /dev/null +++ b/tests/playwright/rte/redis-enterprise/Dockerfile @@ -0,0 +1,9 @@ +FROM redislabs/redis:6.2.8-50 + +## Set the env var to instruct RE to create a cluster on startup +ENV BOOTSTRAP_ACTION create_cluster +ENV BOOTSTRAP_CLUSTER_FQDN cluster.local + +COPY entrypoint.sh db.json ./ + +ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/tests/playwright/rte/redis-enterprise/db.json b/tests/playwright/rte/redis-enterprise/db.json new file mode 100644 index 0000000000..97fe1df45b --- /dev/null +++ b/tests/playwright/rte/redis-enterprise/db.json @@ -0,0 +1,6 @@ +{ + "name": "test-re-standalone", + "type": "redis", + "memory_size": 268435456, + "port": 12000 +} diff --git a/tests/playwright/rte/redis-enterprise/entrypoint.sh b/tests/playwright/rte/redis-enterprise/entrypoint.sh new file mode 100644 index 0000000000..70b5c44bdb --- /dev/null +++ b/tests/playwright/rte/redis-enterprise/entrypoint.sh @@ -0,0 +1,42 @@ +#! /bin/bash + +TEST_RE_USER=${TEST_RE_USER:-"demo@redislabs.com"} +TEST_RE_PASS=${TEST_RE_PASS:-"123456"} + +set -e + +# enable job control +set -m + +/opt/start.sh & + +# This command queries the REST API and outputs the status code +CURL_CMD="curl --silent --fail --output /dev/null -i -w %{http_code} -u $TEST_RE_USER:$TEST_RE_PASS -k https://localhost:9443/v1/nodes" + +# Wait to get 2 consecutive 200 responses from the REST API +while true +do + echo yay $CURL_CMD + CURL_CMD_OUTPUT=$($CURL_CMD || true) + if [ $CURL_CMD_OUTPUT == "200" ] + then + echo "Got 200 response, trying again in 5 seconds to verify..." + sleep 5 + if [ $($CURL_CMD || true) == "200" ] + then + echo "Got 200 response after 5 seconds again, proceeding..." + break + fi + else + echo "Did not get 200 response, got $CURL_CMD_OUTPUT, trying again in 10 seconds..." + sleep 10 + fi +done + +echo "Creating databases..." + +curl -k -u "$TEST_RE_USER:$TEST_RE_PASS" --request POST --url "https://localhost:9443/v1/bdbs" --header 'content-type: application/json' --data-binary "@db.json" + +# now we bring the primary process back into the foreground +# and leave it there +fg diff --git a/tests/playwright/rte/ssh/keys/pub/test.pub b/tests/playwright/rte/ssh/keys/pub/test.pub new file mode 100644 index 0000000000..154d1f0e20 --- /dev/null +++ b/tests/playwright/rte/ssh/keys/pub/test.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPXS0xkxY7o+MUNBJJnf6fKh6AFFpzB0YIfifHSSseXw diff --git a/tests/playwright/rte/ssh/keys/pub/testp.pub b/tests/playwright/rte/ssh/keys/pub/testp.pub new file mode 100644 index 0000000000..4412a28b15 --- /dev/null +++ b/tests/playwright/rte/ssh/keys/pub/testp.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEs/ewkUXl0+uDr7hxSM2vURqdRNFHm7+x05azzW/Yzu diff --git a/tests/playwright/rte/ssh/keys/test b/tests/playwright/rte/ssh/keys/test new file mode 100644 index 0000000000..850c7de9e6 --- /dev/null +++ b/tests/playwright/rte/ssh/keys/test @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACD10tMZMWO6PjFDQSSZ3+nyoegBRacwdGCH4nx0krHl8AAAAKBv1saEb9bG +hAAAAAtzc2gtZWQyNTUxOQAAACD10tMZMWO6PjFDQSSZ3+nyoegBRacwdGCH4nx0krHl8A +AAAEDyew1DnmWamAr0OrUM87FauJfFfea+pi8ctpKNnurNi/XS0xkxY7o+MUNBJJnf6fKh +6AFFpzB0YIfifHSSseXwAAAAG3pvem9Aem96by1IUC1Qcm9Cb29rLTQ1MC1HNwEC +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/playwright/rte/ssh/keys/testp b/tests/playwright/rte/ssh/keys/testp new file mode 100644 index 0000000000..1da447a570 --- /dev/null +++ b/tests/playwright/rte/ssh/keys/testp @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBPcEHCGN +DrMHhpQnPwc0XwAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIEs/ewkUXl0+uDr7 +hxSM2vURqdRNFHm7+x05azzW/YzuAAAAoEhNzctHXM6YBV0z4zzvdniQ5cLwsv8TfMZp2G +WUhZU05yugvKlRu1pml5q3XGSP5wYCF4vvi4BE563PMDKZWAqFFGtiTotEn+XuD/eP+P8H +xdf91tV5kE+1yvVwxUNMcijHY0uYopnG2NN3bdjOH/4YmW0WLyDu10EoMZKVnrP0qBbOrR +xKIy5lqa39SrAnUnGSoTEJsEWGLiIS2rBhkVc= +-----END OPENSSH PRIVATE KEY----- From 1865312c3b6d11f2339db86431bf6b5c2cdfd07a Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 09:48:11 +0200 Subject: [PATCH 010/128] clicking on a db --- tests/playwright/fixtures/open-ri.ts | 8 ++++++-- tests/playwright/pageObjects/base-overview-page.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 9ce6b37abc..ccb6702dfd 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -2,7 +2,8 @@ import { test as base } from '@playwright/test' import BasePage from '../pageObjects/base-page' import {UserAgreementDialog} from '../pageObjects/user-agreement-dialog' import {DatabaseAPIRequests} from'../helpers/api/api-databases' -import {apiUrl, ossStandaloneConfig} from "../helpers/conf"; +import {apiUrl, ossStandaloneConfig} from '../helpers/conf' +import {MyRedisDatabasePage} from '../pageObjects/my-redis-databases-page' type OpenRedisInsight = { basePage: BasePage @@ -36,10 +37,13 @@ export const test = base.extend({ await dbApi.addNewStandaloneDatabaseApi(ossStandaloneConfig) // const page = await context.newPage() - //Navigate to page + // Navigate to page const basePage = new BasePage(page) await basePage.navigateToHomeUrl() + const myDbPage = new MyRedisDatabasePage(page) + await myDbPage.clickOnDBByName(ossStandaloneConfig.databaseName) + await use(basePage) }, diff --git a/tests/playwright/pageObjects/base-overview-page.ts b/tests/playwright/pageObjects/base-overview-page.ts index f8fc59d120..3d94c6b763 100644 --- a/tests/playwright/pageObjects/base-overview-page.ts +++ b/tests/playwright/pageObjects/base-overview-page.ts @@ -2,7 +2,7 @@ import { expect, Locator, Page } from '@playwright/test' import { Toast } from './components/common/toast' import BasePage from './base-page' import { RedisOverviewPage } from '../helpers/constants' -import { DatabaseAPIRequests } from '../helpers/api/api-database' +import { DatabaseAPIRequests } from '../helpers/api/api-databases' export type DatabasesForImport = { host?: string, From 732bf1b97e12ab4a9974df9f1e6037c100a6922b Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 11:12:05 +0200 Subject: [PATCH 011/128] created types and extracting --- tests/playwright/helpers/api/api-databases.ts | 153 ++++++++++-------- tests/playwright/helpers/api/api-keys.ts | 98 +++++++++++ .../pageObjects/base-overview-page.ts | 20 +-- .../dialogs/add-redis-database-dialog.ts | 49 +----- tests/playwright/types/connections.ts | 27 ++++ tests/playwright/types/databese-types.ts | 39 +++++ tests/playwright/types/index.ts | 2 + 7 files changed, 256 insertions(+), 132 deletions(-) create mode 100755 tests/playwright/helpers/api/api-keys.ts create mode 100644 tests/playwright/types/connections.ts create mode 100644 tests/playwright/types/databese-types.ts create mode 100644 tests/playwright/types/index.ts diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 724e535291..43ff51ecf4 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -1,91 +1,78 @@ -// import { sendPostRequest } from './api-common' +// +// import { faker } from '@faker-js/faker' +// import { HttpClient } from './http-client' // import { AddNewDatabaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' // import { ResourcePath } from '../constants' -// import {faker} from "@faker-js/faker" -// // // export class DatabaseAPIRequests { +// private httpClient: HttpClient +// +// constructor(baseURL: string) { +// this.httpClient = new HttpClient(baseURL) +// } +// // /** -// * Add a new Standalone database through api using host and port +// * Add a new standalone database using the HTTP client. // * @param databaseParameters The database parameters +// * @param isCloud Whether the database is cloud-based // */ // async addNewStandaloneDatabaseApi( -// databaseParameters: AddNewDatabaseParameters, isCloud = false +// databaseParameters: AddNewDatabaseParameters, +// isCloud = false // ): Promise { // const uniqueId = faker.string.alphanumeric({ length: 10 }) // const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) -// const requestBody: { -// name?: string, -// host: string, -// port: number, -// username?: string, -// password?: string, -// tls?: boolean, -// verifyServerCert?: boolean, -// caCert?: { -// name: string, -// certificate?: string -// }, -// clientCert?: { -// name: string, -// certificate?: string, -// key?: string -// }, -// cloudDetails?: { -// cloudId: number, -// subscriptionType: string, -// planMemoryLimit: number, -// memoryLimitMeasurementUnit: string, -// free: boolean -// } -// } = { +// +// const requestBody: any = { // name: databaseParameters.databaseName, // host: databaseParameters.host, // port: Number(databaseParameters.port), // username: databaseParameters.databaseUsername, -// password: databaseParameters.databasePassword +// password: databaseParameters.databasePassword, // } // -// if (databaseParameters.caCert) { +// if (databaseParameters.caCert && databaseParameters.clientCert) { // requestBody.tls = true // requestBody.verifyServerCert = false // requestBody.caCert = { -// name: `ca}-${uniqueId}`, -// certificate: databaseParameters.caCert.certificate +// name: `ca-${uniqueId}`, +// certificate: databaseParameters.caCert.certificate, // } // requestBody.clientCert = { -// name: `client}-${uniqueId}`, -// certificate: databaseParameters.clientCert!.certificate, -// key: databaseParameters.clientCert!.key +// name: `client-${uniqueId}`, +// certificate: databaseParameters.clientCert.certificate, +// key: databaseParameters.clientCert.key, // } // } // -// if(isCloud) { +// if (isCloud) { // requestBody.cloudDetails = { // cloudId: uniqueIdNumber, // subscriptionType: 'fixed', -// planMemoryLimit: 30, +// planMemoryLimit: 100, // memoryLimitMeasurementUnit: 'mb', -// free: true +// free: true, // } // } -// const response = await sendPostRequest( -// ResourcePath.Databases, -// requestBody -// ) -// await t -// .expect(await response.body.name) -// .eql( -// databaseParameters.databaseName, -// `Database Name is not equal to ${databaseParameters.databaseName} in response` +// +// const response = await this.httpClient.post(ResourcePath.Databases, requestBody) +// +// if (!response || Object.keys(response).length === 0) { +// throw new Error('The response body is empty') +// } +// +// if (response.name !== databaseParameters.databaseName) { +// throw new Error( +// `Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}` // ) -// await t.expect(await response.status).eql(201) -// } +// } // +// console.log('Database created successfully:', response) +// } // } import { faker } from '@faker-js/faker' import { HttpClient } from './http-client' -import { AddNewDatabaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' +import { AddNewDatabaseParameters, OSSClusterParameters, SentinelParameters, ClusterNodes, databaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' import { ResourcePath } from '../constants' export class DatabaseAPIRequests { @@ -95,15 +82,7 @@ export class DatabaseAPIRequests { this.httpClient = new HttpClient(baseURL) } - /** - * Add a new standalone database using the HTTP client. - * @param databaseParameters The database parameters - * @param isCloud Whether the database is cloud-based - */ - async addNewStandaloneDatabaseApi( - databaseParameters: AddNewDatabaseParameters, - isCloud = false - ): Promise { + async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false): Promise { const uniqueId = faker.string.alphanumeric({ length: 10 }) const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) @@ -146,11 +125,55 @@ export class DatabaseAPIRequests { } if (response.name !== databaseParameters.databaseName) { - throw new Error( - `Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}` - ) + throw new Error(`Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}`) } + } + + async getAllDatabases(): Promise { + return this.httpClient.get(ResourcePath.Databases) + } + + async getDatabaseIdByName(databaseName?: string): Promise { + if (!databaseName) throw new Error('Error: Missing databaseName') + + const allDatabases = await this.getAllDatabases() + const db = allDatabases.find(db => db.name === databaseName) + if (!db) throw new Error(`Database ${databaseName} not found`) + + return db.id + } + + async deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName) + if (!databaseId) throw new Error('Error: Missing databaseId') + + await this.httpClient.delete(ResourcePath.Databases, { ids: [databaseId] }) + } + + async deleteAllDatabasesApi(): Promise { + const allDatabases = await this.getAllDatabases() + const databaseIds = allDatabases.map(db => db.id) + + if (databaseIds.length) { + await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) + await this.deleteAllDatabasesByConnectionTypeApi('SENTINEL') + } + } + + async deleteAllDatabasesByConnectionTypeApi(connectionType: string): Promise { + const allDatabases = await this.getAllDatabases() + const databaseIds = allDatabases.filter(db => db.connectionType === connectionType).map(db => db.id) + + if (databaseIds.length) { + await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) + } + } + + async getClusterNodesApi(databaseParameters: OSSClusterParameters): Promise { + const databaseId = await this.getDatabaseIdByName(databaseParameters.ossClusterDatabaseName) + const resourcePath = `${ResourcePath.Databases}/${databaseId}${ResourcePath.ClusterDetails}` + const response = await this.httpClient.get<{ nodes: ClusterNodes[] }>(resourcePath) - console.log('Database created successfully:', response) + return response.nodes.map(node => `${node.host}:${node.port}`) } } diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts new file mode 100755 index 0000000000..ea614c3117 --- /dev/null +++ b/tests/playwright/helpers/api/api-keys.ts @@ -0,0 +1,98 @@ +import { HttpClient } from './http-client' +import { DatabaseAPIRequests } from './api-databases' +import { AddNewDatabaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' +import { + HashKeyParameters, + StringKeyParameters, + ListKeyParameters, + SetKeyParameters, + SortedSetKeyParameters, + StreamKeyParameters +} from '../../pageObjects/browser-page' + +const databaseAPIRequests = new DatabaseAPIRequests() +const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' + +export class APIKeyRequests { + private httpClient: HttpClient + + constructor(baseURL: string) { + this.httpClient = new HttpClient(baseURL) + } + + private async getDatabaseId(databaseName: string): Promise { + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + if (!databaseId) throw new Error(`Database with name ${databaseName} not found`) + return databaseId + } + + async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + fields: keyParameters.fields.map(fields => ({ + ...fields, + field: Buffer.from(fields.field, 'utf-8'), + value: Buffer.from(fields.value, 'utf-8') + })) + } + + await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) + } + + async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + entries: keyParameters.entries.map(member => ({ + ...member, + fields: member.fields.map(({ name, value }) => ({ + name: Buffer.from(name, 'utf-8'), + value: Buffer.from(value, 'utf-8') + })) + })) + } + + await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) + } + + async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) + } + + await this.httpClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) + } + + async addSortedSetKeyApi(keyParameters: SortedSetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + members: keyParameters.members.map(member => ({ ...member, name: Buffer.from(member.name, 'utf-8') })) + } + + await this.httpClient.post(`/databases/${databaseId}/zSet?encoding=buffer`, requestBody) + } + + async addListKeyApi(keyParameters: ListKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + element: Buffer.from(keyParameters.element, 'utf-8') + } + + await this.httpClient.post(`/databases/${databaseId}/list?encoding=buffer`, requestBody) + } + + async addStringKeyApi(keyParameters: StringKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + value: Buffer.from(keyParameters.value, 'utf-8') + } + + await this.httpClient.post(`/databases/${databaseId}/string?encoding=buffer`, requestBody) + } +} diff --git a/tests/playwright/pageObjects/base-overview-page.ts b/tests/playwright/pageObjects/base-overview-page.ts index 3d94c6b763..2ad48c9194 100644 --- a/tests/playwright/pageObjects/base-overview-page.ts +++ b/tests/playwright/pageObjects/base-overview-page.ts @@ -3,26 +3,8 @@ import { Toast } from './components/common/toast' import BasePage from './base-page' import { RedisOverviewPage } from '../helpers/constants' import { DatabaseAPIRequests } from '../helpers/api/api-databases' +import { DatabasesForImport } from '../types' -export type DatabasesForImport = { - host?: string, - port?: number | string, - name?: string, - result?: string, - username?: string, - auth?: string, - cluster?: boolean | string, - indName?: string, - db?: number, - ssh_port?: number, - timeout_connect?: number, - timeout_execute?: number, - other_field?: string, - ssl?: boolean, - ssl_ca_cert_path?: string, - ssl_local_cert_path?: string, - ssl_private_key_path?: string -}[] export class BaseOverviewPage extends BasePage { // Component instance used in methods diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts index eecc54e8a4..17b775b915 100644 --- a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts @@ -1,55 +1,8 @@ import { expect, Locator, Page } from '@playwright/test' import { TlsCertificates } from '../../helpers/constants' import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel' +import {SentinelParameters, AddNewDatabaseParameters, SSHParameters } from '../../types' -export type AddNewDatabaseParameters = { - host: string - port: string - databaseName?: string - databaseUsername?: string - databasePassword?: string - // For OSS Cluster parameters, you might use these fields: - ossClusterHost?: string - ossClusterPort?: string - ossClusterDatabaseName?: string - caCert?: { - name?: string - certificate?: string - } - clientCert?: { - name?: string - certificate?: string - key?: string - } -} - -export type SentinelParameters = { - sentinelHost: string - sentinelPort: string - masters?: { - alias?: string - db?: string - name?: string - password?: string - }[] - sentinelPassword?: string - name?: string[] -} - -export type OSSClusterParameters = { - ossClusterHost: string - ossClusterPort: string - ossClusterDatabaseName: string -} - -export type SSHParameters = { - sshHost: string - sshPort: string - sshUsername: string - sshPassword?: string - sshPrivateKey?: string - sshPassphrase?: string -} export class AddRedisDatabaseDialog { private readonly page: Page diff --git a/tests/playwright/types/connections.ts b/tests/playwright/types/connections.ts new file mode 100644 index 0000000000..4757d6d283 --- /dev/null +++ b/tests/playwright/types/connections.ts @@ -0,0 +1,27 @@ +export type SentinelParameters = { + sentinelHost: string + sentinelPort: string + masters?: { + alias?: string + db?: string + name?: string + password?: string + }[] + sentinelPassword?: string + name?: string[] +} + +export type OSSClusterParameters = { + ossClusterHost: string + ossClusterPort: string + ossClusterDatabaseName: string +} + +export type SSHParameters = { + sshHost: string + sshPort: string + sshUsername: string + sshPassword?: string + sshPrivateKey?: string + sshPassphrase?: string +} diff --git a/tests/playwright/types/databese-types.ts b/tests/playwright/types/databese-types.ts new file mode 100644 index 0000000000..58867f2768 --- /dev/null +++ b/tests/playwright/types/databese-types.ts @@ -0,0 +1,39 @@ +export type DatabasesForImport = { + host?: string, + port?: number | string, + name?: string, + result?: string, + username?: string, + auth?: string, + cluster?: boolean | string, + indName?: string, + db?: number, + ssh_port?: number, + timeout_connect?: number, + timeout_execute?: number, + other_field?: string, + ssl?: boolean, + ssl_ca_cert_path?: string, + ssl_local_cert_path?: string, + ssl_private_key_path?: string +}[] +export type AddNewDatabaseParameters = { + host: string + port: string + databaseName?: string + databaseUsername?: string + databasePassword?: string + // For OSS Cluster parameters, you might use these fields: + ossClusterHost?: string + ossClusterPort?: string + ossClusterDatabaseName?: string + caCert?: { + name?: string + certificate?: string + } + clientCert?: { + name?: string + certificate?: string + key?: string + } +} diff --git a/tests/playwright/types/index.ts b/tests/playwright/types/index.ts new file mode 100644 index 0000000000..8433104752 --- /dev/null +++ b/tests/playwright/types/index.ts @@ -0,0 +1,2 @@ +export * from './databese-types' +export * from './connections' From 9d9968fb46e95451f2fad3c70753d22d2cca4ac1 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 12:41:29 +0200 Subject: [PATCH 012/128] wip --- tests/playwright/fixtures/open-ri.ts | 7 +- tests/playwright/helpers/api/api-databases.ts | 2 +- tests/playwright/helpers/api/api-keys.ts | 11 +- .../components/redis-cloud-sign-in-panel.ts | 21 +++ tests/playwright/tests/example.spec.ts | 10 +- tests/playwright/types/connections.ts | 10 ++ .../types/{databese-types.ts => databes.ts} | 18 +++ tests/playwright/types/index.ts | 3 +- tests/playwright/types/keys.ts | 137 ++++++++++++++++++ 9 files changed, 205 insertions(+), 14 deletions(-) create mode 100644 tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts rename tests/playwright/types/{databese-types.ts => databes.ts} (65%) create mode 100644 tests/playwright/types/keys.ts diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index ccb6702dfd..6d245dff40 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -10,11 +10,14 @@ type OpenRedisInsight = { dialogUserAgreement: UserAgreementDialog // dbAPI: DatabaseAPIRequests apiUrl: string + dbConfig: object } export const test = base.extend({ - + dbConfig: async ({page}, use) => { + use(ossStandaloneConfig) + }, apiUrl: ['default', { option: true }], // dbAPI: async () => { // const dbApi = new DatabaseAPIRequests(this.apiUrl) @@ -34,7 +37,7 @@ export const test = base.extend({ // Set up the fixture. // Add new database const dbApi = new DatabaseAPIRequests(apiUrl) - await dbApi.addNewStandaloneDatabaseApi(ossStandaloneConfig) + await dbApi.addNewStandaloneDatabaseApi(dbCon) // const page = await context.newPage() // Navigate to page diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 43ff51ecf4..0c033ae8a9 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -72,7 +72,7 @@ // } import { faker } from '@faker-js/faker' import { HttpClient } from './http-client' -import { AddNewDatabaseParameters, OSSClusterParameters, SentinelParameters, ClusterNodes, databaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' +import { AddNewDatabaseParameters, OSSClusterParameters, ClusterNodes, databaseParameters } from '../../types' import { ResourcePath } from '../constants' export class DatabaseAPIRequests { diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index ea614c3117..5aae22d52a 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -1,27 +1,28 @@ import { HttpClient } from './http-client' import { DatabaseAPIRequests } from './api-databases' -import { AddNewDatabaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' import { + AddNewDatabaseParameters, HashKeyParameters, StringKeyParameters, ListKeyParameters, SetKeyParameters, SortedSetKeyParameters, - StreamKeyParameters -} from '../../pageObjects/browser-page' + StreamKeyParameters} from '../../types' + -const databaseAPIRequests = new DatabaseAPIRequests() const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' export class APIKeyRequests { private httpClient: HttpClient + private databaseAPIRequests : DatabaseAPIRequests constructor(baseURL: string) { this.httpClient = new HttpClient(baseURL) + this.databaseAPIRequests = new DatabaseAPIRequests(baseURL) } private async getDatabaseId(databaseName: string): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) if (!databaseId) throw new Error(`Database with name ${databaseName} not found`) return databaseId } diff --git a/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts new file mode 100644 index 0000000000..d38060c7c8 --- /dev/null +++ b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts @@ -0,0 +1,21 @@ +import { Locator, Page } from '@playwright/test' + +export class RedisCloudSigninPanel { + private readonly ssoOauthButton: Locator + private readonly ssoEmailInput: Locator + private readonly submitBtn: Locator + private readonly oauthAgreement: Locator + private readonly googleOauth: Locator + private readonly githubOauth: Locator + private readonly ssoOauth: Locator + + constructor(page: Page) { + this.ssoOauthButton = page.getByTestId('sso-oauth') + this.ssoEmailInput = page.getByTestId('sso-email') + this.submitBtn = page.getByTestId('btn-submit') + this.oauthAgreement = page.locator('[for="ouath-agreement"]') + this.googleOauth = page.getByTestId('google-oauth') + this.githubOauth = page.getByTestId('github-oauth') + this.ssoOauth = page.getByTestId('sso-oauth') + } +} diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 9e981da25f..bd4ea147fa 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '../fixtures/open-ri' -test.beforeEach(async ({ basePage , dialogUserAgreement}) => { - await dialogUserAgreement.acceptLicenseTerms() - await basePage.getText('sa') - -}) +// test.beforeEach(async ({ basePage , dialogUserAgreement}) => { +// await dialogUserAgreement.acceptLicenseTerms() +// await basePage.getText('sa') +// +// }) test('basic test', async ({ basePage, page }) => { await basePage.click('button') diff --git a/tests/playwright/types/connections.ts b/tests/playwright/types/connections.ts index 4757d6d283..6a82170fe9 100644 --- a/tests/playwright/types/connections.ts +++ b/tests/playwright/types/connections.ts @@ -25,3 +25,13 @@ export type SSHParameters = { sshPrivateKey?: string sshPassphrase?: string } + +/** + * Nodes in OSS Cluster parameters + * @param host The host of the node + * @param port The port of the node + */ +export type ClusterNodes = { + host: string, + port: string +}; diff --git a/tests/playwright/types/databese-types.ts b/tests/playwright/types/databes.ts similarity index 65% rename from tests/playwright/types/databese-types.ts rename to tests/playwright/types/databes.ts index 58867f2768..7bd6495758 100644 --- a/tests/playwright/types/databese-types.ts +++ b/tests/playwright/types/databes.ts @@ -37,3 +37,21 @@ export type AddNewDatabaseParameters = { key?: string } } + +/** + * Already existing database parameters + * @param id The id of the database + * @param host The host of the database + * @param port The port of the database + * @param name The name of the database + * @param connectionType The connection type of the database + * @param lastConnection The last connection time of the database + */ +export type databaseParameters = { + id: string, + host?: string, + port?: string, + name?: string, + connectionType?: string, + lastConnection?: string +} diff --git a/tests/playwright/types/index.ts b/tests/playwright/types/index.ts index 8433104752..c49b877a2b 100644 --- a/tests/playwright/types/index.ts +++ b/tests/playwright/types/index.ts @@ -1,2 +1,3 @@ -export * from './databese-types' +export * from './databes' export * from './connections' +export * from './keys' diff --git a/tests/playwright/types/keys.ts b/tests/playwright/types/keys.ts new file mode 100644 index 0000000000..687b8da67c --- /dev/null +++ b/tests/playwright/types/keys.ts @@ -0,0 +1,137 @@ +/** + * Add new keys parameters + * @param keyName The name of the key + * @param TTL The ttl of the key + * @param value The value of the key + * @param members The members of the key + * @param scores The scores of the key member + * @param field The field of the key + */ +export type AddNewKeyParameters = { + keyName: string, + value?: string, + TTL?: string, + members?: string, + scores?: string, + field?: string, + fields?: [{ + field?: string, + valuse?: string + }] +} + +/** + * Hash key parameters + * @param keyName The name of the key + * @param fields The Array with fields + * @param field The field of the field + * @param value The value of the field + + */ +export type HashKeyParameters = { + keyName: string, + fields: { + field: string, + value: string + }[] +} + +/** + * Stream key parameters + * @param keyName The name of the key + * @param entries The Array with entries + * @param id The id of entry + * @param fields The Array with fields + */ +export type StreamKeyParameters = { + keyName: string, + entries: { + id: string, + fields: { + name: string, + value: string + }[] + }[] +} + +/** + * Set key parameters + * @param keyName The name of the key + * @param members The Array with members + */ +export type SetKeyParameters = { + keyName: string, + members: string[] +} + +/** + * Sorted Set key parameters + * @param keyName The name of the key + * @param members The Array with members + * @param name The name of the member + * @param id The id of the member + */ +export type SortedSetKeyParameters = { + keyName: string, + members: { + name: string, + score: number + }[] +} + +/** + * List key parameters + * @param keyName The name of the key + * @param element The element in list + */ +export type ListKeyParameters = { + keyName: string, + element: string +} + +/** + * String key parameters + * @param keyName The name of the key + * @param value The value in the string + */ +export type StringKeyParameters = { + keyName: string, + value: string +} + +/** + * The key arguments for multiple keys/fields adding + * @param keysCount The number of keys to add + * @param fieldsCount The number of fields in key to add + * @param elementsCount The number of elements in key to add + * @param membersCount The number of members in key to add + * @param keyName The full key name + * @param keyNameStartWith The name of key should start with + * @param fieldStartWitht The name of field should start with + * @param fieldValueStartWith The name of field value should start with + * @param elementStartWith The name of element should start with + * @param memberStartWith The name of member should start with + */ + +export type AddKeyArguments = { + keysCount?: number, + fieldsCount?: number, + elementsCount?: number, + membersCount?: number, + keyName?: string, + keyNameStartWith?: string, + fieldStartWith?: string, + fieldValueStartWith?: string, + elementStartWith?: string, + memberStartWith?: string +} + +/** + * Keys Data parameters + * @param textType The type of the key + * @param keyName The name of the key + */ +export type KeyData = { + textType: string, + keyName: string +}[] From 23af1c9c4f6b2ce2d55814924e1003039d34066e Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 16:04:59 +0200 Subject: [PATCH 013/128] wip --- tests/playwright/fixtures/open-ri.ts | 15 ++++++++------- tests/playwright/helpers/api/http-client.ts | 3 +++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 6d245dff40..48b6aac145 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -2,7 +2,7 @@ import { test as base } from '@playwright/test' import BasePage from '../pageObjects/base-page' import {UserAgreementDialog} from '../pageObjects/user-agreement-dialog' import {DatabaseAPIRequests} from'../helpers/api/api-databases' -import {apiUrl, ossStandaloneConfig} from '../helpers/conf' +import { ossStandaloneConfig} from '../helpers/conf' import {MyRedisDatabasePage} from '../pageObjects/my-redis-databases-page' type OpenRedisInsight = { @@ -10,13 +10,14 @@ type OpenRedisInsight = { dialogUserAgreement: UserAgreementDialog // dbAPI: DatabaseAPIRequests apiUrl: string - dbConfig: object + dbConfig: typeof ossStandaloneConfig } export const test = base.extend({ - dbConfig: async ({page}, use) => { - use(ossStandaloneConfig) + dbConfig: async ({}, use) => { + console.log('Fixture setup: Assigning database config') + await use(ossStandaloneConfig) // Use the imported object directly }, apiUrl: ['default', { option: true }], // dbAPI: async () => { @@ -33,11 +34,11 @@ export const test = base.extend({ // await context.close() // }, // basePage: async ({ context }, use) => { - basePage: async ({ page }, use) => { + basePage: async ({ page , dbConfig, apiUrl }, use, testInfo) => { // Set up the fixture. // Add new database const dbApi = new DatabaseAPIRequests(apiUrl) - await dbApi.addNewStandaloneDatabaseApi(dbCon) + await dbApi.addNewStandaloneDatabaseApi(dbConfig) // const page = await context.newPage() // Navigate to page @@ -45,7 +46,7 @@ export const test = base.extend({ await basePage.navigateToHomeUrl() const myDbPage = new MyRedisDatabasePage(page) - await myDbPage.clickOnDBByName(ossStandaloneConfig.databaseName) + await myDbPage.clickOnDBByName(dbConfig.databaseName) await use(basePage) diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index 7333bc7ce1..100aab7349 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -19,6 +19,9 @@ export class HttpClient { private instance: AxiosInstance constructor(baseURL: string) { + if (!baseURL) { + throw new Error('No URL must be passed in') + } this.instance = axios.create({ baseURL, httpsAgent: new https.Agent({ From a7520b5886c7de103a120962dd42675aa8695104 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 19:10:54 +0200 Subject: [PATCH 014/128] wip --- tests/playwright/fixtures/open-ri.ts | 14 ++++++++++---- tests/playwright/tests/example.spec.ts | 21 ++++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 48b6aac145..a3b5821bdb 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -11,10 +11,10 @@ type OpenRedisInsight = { // dbAPI: DatabaseAPIRequests apiUrl: string dbConfig: typeof ossStandaloneConfig - + forEachWorker: void } -export const test = base.extend({ +export const test = base.extend({ dbConfig: async ({}, use) => { console.log('Fixture setup: Assigning database config') await use(ossStandaloneConfig) // Use the imported object directly @@ -34,7 +34,7 @@ export const test = base.extend({ // await context.close() // }, // basePage: async ({ context }, use) => { - basePage: async ({ page , dbConfig, apiUrl }, use, testInfo) => { + basePage: async ({ page , dbConfig, apiUrl }, use) => { // Set up the fixture. // Add new database const dbApi = new DatabaseAPIRequests(apiUrl) @@ -56,7 +56,13 @@ export const test = base.extend({ await userAgreementDialog.acceptLicenseTerms() await use(new UserAgreementDialog(page)) }, - + forEachWorker: [async ({}, use) => { + // This code runs before all the tests in the worker process. + console.log(`BEFORE Starting test worker ${test.info().workerIndex}`) + await use() + // This code runs after all the tests in the worker process. + console.log(`Stopping test worker ${test.info().workerIndex}`) + }, { scope: 'worker', auto: true }], // automatically starts for every worker. }) export { expect } from '@playwright/test' diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index bd4ea147fa..5a7954277b 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,12 +1,19 @@ import { test, expect } from '../fixtures/open-ri' -// test.beforeEach(async ({ basePage , dialogUserAgreement}) => { -// await dialogUserAgreement.acceptLicenseTerms() -// await basePage.getText('sa') -// -// }) +test.beforeEach(async ({ basePage , dialogUserAgreement}) => { + // await dialogUserAgreement.acceptLicenseTerms() + // await basePage.getText('sa') + console.log("WE ARE IN THE BEFORE STEP") +}) + +test.afterEach(async ({ basePage , dialogUserAgreement}) => { + // await dialogUserAgreement.acceptLicenseTerms() + // await basePage.getText('sa') + console.log('WE ARE IN THE AFTER STEP') +}) test('basic test', async ({ basePage, page }) => { - await basePage.click('button') - await expect(page.getByTestId('todo-title')).toContainText(['something nice']) + // await basePage.click('button') + // await expect(page.getByTestId('todo-title')).toContainText(['something nice']) + console.log('WE ARE IN TEST') }) From 4d8111b609cb569d954160e2267530177a203af2 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 20:50:49 +0200 Subject: [PATCH 015/128] wip --- tests/playwright/fixtures/open-ri.ts | 201 +++++++++++++----- tests/playwright/helpers/api/api-databases.ts | 2 +- tests/playwright/helpers/api/api-keys.ts | 115 ++++++---- tests/playwright/helpers/api/http-client.ts | 6 +- .../pageObjects/user-agreement-dialog.ts | 2 +- 5 files changed, 233 insertions(+), 93 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index a3b5821bdb..326ad88655 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -1,68 +1,169 @@ +// import { test as base } from '@playwright/test' +// import BasePage from '../pageObjects/base-page' +// import {UserAgreementDialog} from '../pageObjects/user-agreement-dialog' +// import {DatabaseAPIRequests} from'../helpers/api/api-databases' +// import { ossStandaloneConfig} from '../helpers/conf' +// import {MyRedisDatabasePage} from '../pageObjects/my-redis-databases-page' +// import { APIKeyRequests } from '../helpers/api/api-keys' +// +// type OpenRedisInsight = { +// basePage: BasePage +// dialogUserAgreement: UserAgreementDialog +// // dbAPI: DatabaseAPIRequests +// apiUrl: string +// dbConfig: typeof ossStandaloneConfig +// forEachWorker: void +// } +// +// export const test = base.extend< OpenRedisInsight, +// { +// forEachWorker: void, +// apiUrl:string +// dbConfig: typeof ossStandaloneConfig}> +// ({ +// dbConfig: async ({}, use) => { +// console.log('Fixture setup: Assigning database config') +// await use(ossStandaloneConfig) // Use the imported object directly +// }, +// apiUrl: ['default', { option: true }], +// // dbAPI: async () => { +// // const dbApi = new DatabaseAPIRequests(this.apiUrl) +// // +// // await dbApi.addNewStandaloneDatabaseApi(ossStandaloneConfig) +// // }, +// // context: async ({ browser }, use) => { +// // const context = await browser.newContext() +// // await context.clearCookies() +// // await context.clearPermissions() +// // // await context.storageState({ path: 'emptyState.json' }) +// // await use(context) +// // await context.close() +// // }, +// // basePage: async ({ context }, use) => { +// basePage: async ({ page , dbConfig }, use) => { +// +// +// // const page = await context.newPage() +// // Navigate to page +// const basePage = new BasePage(page) +// await basePage.navigateToHomeUrl() +// +// const myDbPage = new MyRedisDatabasePage(page) +// await myDbPage.clickOnDBByName(dbConfig.databaseName) +// +// await use(basePage) +// +// }, +// dialogUserAgreement: async ({ page }, use) => { +// const userAgreementDialog = new UserAgreementDialog(page) +// // await userAgreementDialog.acceptLicenseTerms() +// await use(new UserAgreementDialog(page)) +// }, +// forEachWorker: [async ({ apiUrl, dbConfig }, use) => { +// // This code runs before all the tests in the worker process. +// const ti = test.info().workerIndex +// console.log(`BEFORE Starting test worker ${ti}`) +// // Set up the fixture. +// // Add new database +// const dbApi = new DatabaseAPIRequests(apiUrl) +// await dbApi.addNewStandaloneDatabaseApi(dbConfig) +// await use() +// // This code runs after all the tests in the worker process. +// console.log(`Stopping test worker ${ti}`) +// const apiKeyClient = new APIKeyRequests(apiUrl) +// // apiKeyClient.deleteKeyByNameApi() +// await dbApi.deleteStandaloneDatabaseApi(dbConfig) +// }, { auto: true }], // automatically starts for every worker. +// }) +// +// export { expect } from '@playwright/test' + import { test as base } from '@playwright/test' import BasePage from '../pageObjects/base-page' -import {UserAgreementDialog} from '../pageObjects/user-agreement-dialog' -import {DatabaseAPIRequests} from'../helpers/api/api-databases' -import { ossStandaloneConfig} from '../helpers/conf' -import {MyRedisDatabasePage} from '../pageObjects/my-redis-databases-page' +import { UserAgreementDialog } from '../pageObjects/user-agreement-dialog' +import { DatabaseAPIRequests } from '../helpers/api/api-databases' +import { ossStandaloneConfig } from '../helpers/conf' +import { MyRedisDatabasePage } from '../pageObjects/my-redis-databases-page' +import { APIKeyRequests } from '../helpers/api/api-keys' + +// Define shared worker object +type WorkerSharedState = { + apiUrl: string; + dbConfig: typeof ossStandaloneConfig; + baseUrl: string; +} +// Define test fixture types type OpenRedisInsight = { - basePage: BasePage - dialogUserAgreement: UserAgreementDialog - // dbAPI: DatabaseAPIRequests - apiUrl: string - dbConfig: typeof ossStandaloneConfig - forEachWorker: void + basePage: BasePage; + dialogUserAgreement: UserAgreementDialog; + workerState: WorkerSharedState; // Worker-scoped object passed to tests } -export const test = base.extend({ - dbConfig: async ({}, use) => { - console.log('Fixture setup: Assigning database config') - await use(ossStandaloneConfig) // Use the imported object directly - }, - apiUrl: ['default', { option: true }], - // dbAPI: async () => { - // const dbApi = new DatabaseAPIRequests(this.apiUrl) - // - // await dbApi.addNewStandaloneDatabaseApi(ossStandaloneConfig) - // }, - // context: async ({ browser }, use) => { - // const context = await browser.newContext() - // await context.clearCookies() - // await context.clearPermissions() - // // await context.storageState({ path: 'emptyState.json' }) - // await use(context) - // await context.close() - // }, - // basePage: async ({ context }, use) => { - basePage: async ({ page , dbConfig, apiUrl }, use) => { - // Set up the fixture. - // Add new database - const dbApi = new DatabaseAPIRequests(apiUrl) - await dbApi.addNewStandaloneDatabaseApi(dbConfig) - - // const page = await context.newPage() - // Navigate to page +// Extend Playwright test +export const test = base.extend< + OpenRedisInsight, + { forEachWorker: void; workerState: WorkerSharedState } // Worker-scoped fixtures +>({ + // ✅ Worker-scoped shared object + workerState: [async ({}, use, testInfo) => { + console.log(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`) + + // Initialize worker-scoped data + const workerState: WorkerSharedState = { + apiUrl: testInfo.project.use.apiUrl, + dbConfig: ossStandaloneConfig, + baseUrl: testInfo.project.use.baseURL + } + + console.log(`🌐 API URL: ${workerState.apiUrl}`) + console.log(`🗄️ Database Config: ${JSON.stringify(workerState.dbConfig)}`) + console.log(`🏠 Base URL: ${workerState.baseUrl}`) + + await use(workerState) + }, { scope: 'worker' }], // Runs once per worker + + // ✅ Worker-scoped setup/teardown + forEachWorker: [async ({ workerState }, use) => { + const ti = base.info().workerIndex + console.log(`BEFORE Starting test worker ${ti}`) + + // Set up the database before tests + const dbApi = new DatabaseAPIRequests(workerState.apiUrl) + await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) + + await use() // Run the tests + + console.log(`Stopping test worker ${ti}`) + + // Cleanup after all tests in this worker + const apiKeyClient = new APIKeyRequests(workerState.apiUrl) + // await apiKeyClient.deleteKeyByNameApi(); + await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + }, { scope: 'worker', auto: true }], + + // ✅ Test-scoped `basePage` using worker state + basePage: async ({ page, workerState }, use) => { + console.log('Fixture setup: Initializing Base Page') + + // Navigate to home page const basePage = new BasePage(page) - await basePage.navigateToHomeUrl() + await basePage.navigateToHomeUrl(workerState.baseUrl) + // Interact with the database UI const myDbPage = new MyRedisDatabasePage(page) - await myDbPage.clickOnDBByName(dbConfig.databaseName) + await myDbPage.clickOnDBByName(workerState.dbConfig.databaseName) await use(basePage) - }, + + // ✅ Test-scoped `dialogUserAgreement` dialogUserAgreement: async ({ page }, use) => { - const userAgreementDialog = new UserAgreementDialog(page) + console.log('Fixture setup: Accepting User Agreement') + const userAgreementDialog = new UserAgreementDialog(page) await userAgreementDialog.acceptLicenseTerms() - await use(new UserAgreementDialog(page)) + await use(userAgreementDialog) }, - forEachWorker: [async ({}, use) => { - // This code runs before all the tests in the worker process. - console.log(`BEFORE Starting test worker ${test.info().workerIndex}`) - await use() - // This code runs after all the tests in the worker process. - console.log(`Stopping test worker ${test.info().workerIndex}`) - }, { scope: 'worker', auto: true }], // automatically starts for every worker. }) export { expect } from '@playwright/test' diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 0c033ae8a9..740dc4a653 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -147,7 +147,7 @@ export class DatabaseAPIRequests { const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName) if (!databaseId) throw new Error('Error: Missing databaseId') - await this.httpClient.delete(ResourcePath.Databases, { ids: [databaseId] }) + await this.httpClient.delete(ResourcePath.Databases, { ids: [`${databaseId}`] }) } async deleteAllDatabasesApi(): Promise { diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index 5aae22d52a..b39c32cfd3 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -14,6 +14,7 @@ const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' export class APIKeyRequests { private httpClient: HttpClient + private databaseAPIRequests : DatabaseAPIRequests constructor(baseURL: string) { @@ -27,6 +28,78 @@ export class APIKeyRequests { return databaseId } + // async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + // const requestBody = { + // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + // fields: keyParameters.fields.map(fields => ({ + // ...fields, + // field: Buffer.from(fields.field, 'utf-8'), + // value: Buffer.from(fields.value, 'utf-8') + // })) + // } + // + // await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) + // } + // + // async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + // const requestBody = { + // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + // entries: keyParameters.entries.map(member => ({ + // ...member, + // fields: member.fields.map(({ name, value }) => ({ + // name: Buffer.from(name, 'utf-8'), + // value: Buffer.from(value, 'utf-8') + // })) + // })) + // } + // + // await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) + // } + // + // async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + // const requestBody = { + // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + // members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) + // } + // + // await this.httpClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) + // } + // + // async addSortedSetKeyApi(keyParameters: SortedSetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + // const requestBody = { + // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + // members: keyParameters.members.map(member => ({ ...member, name: Buffer.from(member.name, 'utf-8') })) + // } + // + // await this.httpClient.post(`/databases/${databaseId}/zSet?encoding=buffer`, requestBody) + // } + // + // async addListKeyApi(keyParameters: ListKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + // const requestBody = { + // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + // element: Buffer.from(keyParameters.element, 'utf-8') + // } + // + // await this.httpClient.post(`/databases/${databaseId}/list?encoding=buffer`, requestBody) + // } + // + // async addStringKeyApi(keyParameters: StringKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + // const requestBody = { + // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + // value: Buffer.from(keyParameters.value, 'utf-8') + // } + // + // await this.httpClient.post(`/databases/${databaseId}/string?encoding=buffer`, requestBody) + // } + + + async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { const databaseId = await this.getDatabaseId(databaseParameters.databaseName) const requestBody = { @@ -57,43 +130,9 @@ export class APIKeyRequests { await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) } - async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - const requestBody = { - keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) - } - - await this.httpClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) - } - - async addSortedSetKeyApi(keyParameters: SortedSetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - const requestBody = { - keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - members: keyParameters.members.map(member => ({ ...member, name: Buffer.from(member.name, 'utf-8') })) - } - - await this.httpClient.post(`/databases/${databaseId}/zSet?encoding=buffer`, requestBody) - } - - async addListKeyApi(keyParameters: ListKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - const requestBody = { - keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - element: Buffer.from(keyParameters.element, 'utf-8') - } - - await this.httpClient.post(`/databases/${databaseId}/list?encoding=buffer`, requestBody) - } - - async addStringKeyApi(keyParameters: StringKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - const requestBody = { - keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - value: Buffer.from(keyParameters.value, 'utf-8') - } - - await this.httpClient.post(`/databases/${databaseId}/string?encoding=buffer`, requestBody) + async deleteKeyByNameApi(keyName: string, databaseName: string): Promise { + const databaseId = await this.getDatabaseId(databaseName) + const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } + await this.httpClient.delete(`/databases/${databaseId}/keys`, requestBody) } } diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index 100aab7349..a54e6f5106 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -19,9 +19,9 @@ export class HttpClient { private instance: AxiosInstance constructor(baseURL: string) { - if (!baseURL) { - throw new Error('No URL must be passed in') - } + // if (!baseURL) { + // throw new Error('No URL must be passed in') + // } this.instance = axios.create({ baseURL, httpsAgent: new https.Agent({ diff --git a/tests/playwright/pageObjects/user-agreement-dialog.ts b/tests/playwright/pageObjects/user-agreement-dialog.ts index d86e1d2386..f0e9008936 100644 --- a/tests/playwright/pageObjects/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/user-agreement-dialog.ts @@ -22,7 +22,7 @@ export class UserAgreementDialog extends BasePage { } async acceptLicenseTerms(): Promise { - if (await this.switchOptionEula.isVisible()) { + if (await this.switchOptionEula.isVisible) { await this.recommendedSwitcher.click() await this.switchOptionEula.click() await this.submitButton.click() From b9c814d0e5130e99fe2d97f42da1b8a645809bfb Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sat, 15 Mar 2025 23:36:38 +0200 Subject: [PATCH 016/128] wip - add key working --- tests/playwright/fixtures/open-ri.ts | 2 +- tests/playwright/pageObjects/base-page.ts | 8 +- tests/playwright/pageObjects/browser-page.ts | 989 +++++++++++++++++++ tests/playwright/tests/example.spec.ts | 20 +- 4 files changed, 1012 insertions(+), 7 deletions(-) create mode 100755 tests/playwright/pageObjects/browser-page.ts diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 326ad88655..62efdad43a 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -137,7 +137,7 @@ export const test = base.extend< console.log(`Stopping test worker ${ti}`) // Cleanup after all tests in this worker - const apiKeyClient = new APIKeyRequests(workerState.apiUrl) + // const apiKeyClient = new APIKeyRequests(workerState.apiUrl) // await apiKeyClient.deleteKeyByNameApi(); await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) }, { scope: 'worker', auto: true }], diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 3c1d1d138f..958b00f0f0 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -1,4 +1,4 @@ -import {Locator, Page} from '@playwright/test' +import {Locator, Page, expect} from '@playwright/test' export default class BasePage { protected page: Page @@ -38,4 +38,10 @@ export default class BasePage { async getByTestId(testId: string): Promise { return this.page.getByTestId(testId) } + async waitForLocatorVisible(locator: Locator, timeout = 6000) { + await expect(locator).toBeVisible({ timeout }) + } + async waitForLocatorNotVisible(locator: Locator, timeout = 6000) { + await expect(locator).not.toBeVisible({ timeout }) + } } diff --git a/tests/playwright/pageObjects/browser-page.ts b/tests/playwright/pageObjects/browser-page.ts new file mode 100755 index 0000000000..e1cae68cb6 --- /dev/null +++ b/tests/playwright/pageObjects/browser-page.ts @@ -0,0 +1,989 @@ +import { expect, Locator, Page } from '@playwright/test' +// import { Common } from '../helpers/common' +import { AddElementInList } from '../helpers/constants' +import {Toast } from './components/common/toast' + +// import { BulkActions, TreeView } from './components/browser' +// import { AddNewKeyParameters , HashKeyParameters ,StreamKeyParameters, +// SetKeyParameters , SortedSetKeyParameters ,ListKeyParameters, StringKeyParameters, AddKeyArguments, KeyData} from '../types' +import BasePage from './base-page' + +export class BrowserPage extends BasePage { + // private readonly bulkActions: BulkActions + // private readonly treeView: TreeView + private page: Page + private toast: Toast + // CSS Selectors + private readonly cssSelectorGrid: Locator + private readonly cssSelectorRows: Locator + private readonly cssSelectorKey: Locator + private readonly cssFilteringLabel: Locator + private readonly cssJsonValue: Locator + private readonly cssRowInVirtualizedTable: Locator + private readonly cssVirtualTableRow: Locator + private readonly cssKeyBadge: Locator + private readonly cssKeyTtl: Locator + private readonly cssKeySize: Locator + private readonly cssRemoveSuggestionItem: Locator + + // BUTTONS + private readonly applyButton: Locator + private readonly deleteKeyButton: Locator + private readonly submitDeleteKeyButton: Locator + private readonly confirmDeleteKeyButton: Locator + private readonly editKeyTTLButton: Locator + private readonly refreshKeysButton: Locator + private readonly refreshKeyButton: Locator + private readonly editKeyNameButton: Locator + private readonly editKeyValueButton: Locator + private readonly closeKeyButton: Locator + private readonly plusAddKeyButton: Locator + private readonly addKeyValueItemsButton: Locator + private readonly saveHashFieldButton: Locator + private readonly saveMemberButton: Locator + private readonly searchButtonInKeyDetails: Locator + private readonly addKeyButton: Locator + private readonly keyTypeDropDown: Locator + private readonly confirmRemoveHashFieldButton: Locator + private readonly removeSetMemberButton: Locator + private readonly removeHashFieldButton: Locator + private readonly removeZsetMemberButton: Locator + private readonly confirmRemoveSetMemberButton: Locator + private readonly confirmRemoveZSetMemberButton: Locator + private readonly saveElementButton: Locator + private readonly removeElementFromListIconButton: Locator + private readonly removeElementFromListButton: Locator + private readonly confirmRemoveListElementButton: Locator + private readonly removeElementFromListSelect: Locator + private readonly addJsonObjectButton: Locator + private readonly addJsonFieldButton: Locator + private readonly expandJsonObject: Locator + private readonly scoreButton: Locator + private readonly sortingButton: Locator + private readonly editJsonObjectButton: Locator + private readonly applyEditButton: Locator + private readonly cancelEditButton: Locator + private readonly scanMoreButton: Locator + private readonly resizeBtnKeyList: Locator + private readonly treeViewButton: Locator + private readonly browserViewButton: Locator + private readonly searchButton: Locator + private readonly clearFilterButton: Locator + private readonly fullScreenModeButton: Locator + private readonly closeRightPanel: Locator + private readonly addNewStreamEntry: Locator + private readonly removeEntryButton: Locator + private readonly confirmRemoveEntryButton: Locator + private readonly clearStreamEntryInputs: Locator + private readonly saveGroupsButton: Locator + private readonly acknowledgeButton: Locator + private readonly confirmAcknowledgeButton: Locator + private readonly claimPendingMessageButton: Locator + private readonly submitButton: Locator + private readonly consumerDestinationSelect: Locator + private readonly removeConsumerButton: Locator + private readonly removeConsumerGroupButton: Locator + private readonly optionalParametersSwitcher: Locator + private readonly forceClaimCheckbox: Locator + private readonly editStreamLastIdButton: Locator + private readonly saveButton: Locator + private readonly bulkActionsButton: Locator + private readonly editHashButton: Locator + private readonly editHashFieldTtlButton: Locator + private readonly editZsetButton: Locator + private readonly editListButton: Locator + private readonly cancelStreamGroupBtn: Locator + private readonly patternModeBtn: Locator + private readonly redisearchModeBtn: Locator + private readonly showFilterHistoryBtn: Locator + private readonly clearFilterHistoryBtn: Locator + private readonly loadSampleDataBtn: Locator + private readonly executeBulkKeyLoadBtn: Locator + private readonly backToBrowserBtn: Locator + private readonly loadAllBtn: Locator + private readonly downloadAllValueBtn: Locator + private readonly openTutorialsBtn: Locator + private readonly keyItem: Locator + private readonly columnsBtn: Locator + + // CONTAINERS + private readonly streamGroupsContainer: Locator + private readonly streamConsumersContainer: Locator + private readonly breadcrumbsContainer: Locator + private readonly virtualTableContainer: Locator + private readonly streamEntriesContainer: Locator + private readonly streamMessagesContainer: Locator + private readonly loader: Locator + private readonly newIndexPanel: Locator + + // LINKS + private readonly internalLinkToWorkbench: Locator + private readonly userSurveyLink: Locator + private readonly redisearchFreeLink: Locator + private readonly guideLinksBtn: Locator + + // OPTION ELEMENTS + private readonly stringOption: Locator + private readonly jsonOption: Locator + private readonly setOption: Locator + private readonly zsetOption: Locator + private readonly listOption: Locator + private readonly hashOption: Locator + private readonly streamOption: Locator + private readonly removeFromHeadSelection: Locator + private readonly filterOptionType: Locator + private readonly filterByKeyTypeDropDown: Locator + private readonly filterAllKeyType: Locator + private readonly consumerOption: Locator + private readonly claimTimeOptionSelect: Locator + private readonly relativeTimeOption: Locator + private readonly timestampOption: Locator + private readonly formatSwitcher: Locator + private readonly formatSwitcherIcon: Locator + private readonly refreshIndexButton: Locator + private readonly selectIndexDdn: Locator + private readonly createIndexBtn: Locator + private readonly cancelIndexCreationBtn: Locator + private readonly confirmIndexCreationBtn: Locator + private readonly resizeTrigger: Locator + private readonly filterHistoryOption: Locator + private readonly filterHistoryItemText: Locator + + // TABS + private readonly streamTabGroups: Locator + private readonly streamTabConsumers: Locator + private readonly streamTabs: Locator + + // TEXT INPUTS + private readonly addKeyNameInput: Locator + private readonly keyNameInput: Locator + private readonly keyTTLInput: Locator + private readonly editKeyTTLInput: Locator + private readonly ttlText: Locator + private readonly hashFieldValueInput: Locator + private readonly hashFieldNameInput: Locator + private readonly hashFieldValueEditor: Locator + private readonly hashTtlFieldInput: Locator + private readonly listKeyElementEditorInput: Locator + private readonly stringKeyValueInput: Locator + private readonly jsonKeyValueInput: Locator + private readonly jsonUploadInput: Locator + private readonly setMemberInput: Locator + private readonly zsetMemberScoreInput: Locator + private readonly filterByPatterSearchInput: Locator + + // TEXT ELEMENTS + private readonly keySizeDetails: Locator + private readonly keyLengthDetails: Locator + private readonly keyNameInTheList: Locator + private readonly hashFieldsList: Locator + private readonly hashValuesList: Locator + private readonly hashField: Locator + private readonly hashFieldValue: Locator + private readonly setMembersList: Locator + private readonly zsetMembersList: Locator + private readonly zsetScoresList: Locator + private readonly listElementsList: Locator + private readonly jsonKeyValue: Locator + private readonly jsonError: Locator + private readonly tooltip: Locator + private readonly dialog: Locator + private readonly noResultsFound: Locator + private readonly noResultsFoundOnly: Locator + private readonly searchAdvices: Locator + private readonly keysNumberOfResults: Locator + private readonly scannedValue: Locator + private readonly totalKeysNumber: Locator + private readonly keyDetailsBadge: Locator + private readonly modulesTypeDetails: Locator + private readonly filteringLabel: Locator + private readonly keysSummary: Locator + private readonly multiSearchArea: Locator + private readonly keyDetailsHeader: Locator + private readonly keyListTable: Locator + private readonly keyListMessage: Locator + private readonly keyDetailsTable: Locator + private readonly keyNameFormDetails: Locator + private readonly keyDetailsTTL: Locator + private readonly progressLine: Locator + private readonly progressKeyList: Locator + private readonly jsonScalarValue: Locator + private readonly noKeysToDisplayText: Locator + private readonly streamEntryDate: Locator + private readonly streamEntryIdValue: Locator + private readonly streamFields: Locator + private readonly streamVirtualContainer: Locator + private readonly streamEntryFields: Locator + private readonly confirmationMessagePopover: Locator + private readonly streamGroupId: Locator + private readonly streamGroupName: Locator + private readonly streamMessage: Locator + private readonly streamConsumerName: Locator + private readonly consumerGroup: Locator + private readonly entryIdInfoIcon: Locator + private readonly entryIdError: Locator + private readonly pendingCount: Locator + private readonly streamRangeBar: Locator + private readonly rangeLeftTimestamp: Locator + private readonly rangeRightTimestamp: Locator + private readonly jsonValue: Locator + private readonly stringValueAsJson: Locator + + // POPUPS + private readonly changeValueWarning: Locator + + // TABLE + private readonly keyListItem: Locator + + // DIALOG + private readonly noReadySearchDialogTitle: Locator + + // CHECKBOXES + private readonly showTtlCheckbox: Locator + private readonly showTtlColumnCheckbox: Locator + private readonly showSizeColumnCheckbox: Locator + + // UTILITY FUNCTIONS + private readonly getHashTtlFieldInput: (fieldName: string) => Locator + private readonly getListElementInput: (count: number) => Locator + private readonly getKeySize: (keyName: string) => Locator + private readonly getKeyTTl: (keyName: string) => Locator + + constructor(page: Page) { + super(page) + this.page = page + this.toast = new Toast(page) + // this.bulkActions = new BulkActions(page) + // this.treeView = new TreeView(page) + + // CSS Selectors + this.cssSelectorGrid = page.locator('[aria-label="grid"]') + this.cssSelectorRows = page.locator('[aria-label="row"]') + this.cssSelectorKey = page.locator('[data-testid^="key-"]') + this.cssFilteringLabel = page.getByTestId('multi-search') + this.cssJsonValue = page.getByTestId('value-as-json') + this.cssRowInVirtualizedTable = page.locator('[role="gridcell"]') + this.cssVirtualTableRow = page.locator('[aria-label="row"]') + this.cssKeyBadge = page.locator('[data-testid^="badge-"]') + this.cssKeyTtl = page.locator('[data-testid^="ttl-"]') + this.cssKeySize = page.locator('[data-testid^="size-"]') + this.cssRemoveSuggestionItem = page.locator('[data-testid^="remove-suggestion-item-"]') + + // BUTTONS + this.applyButton = page.getByTestId('apply-btn') + this.deleteKeyButton = page.getByTestId('delete-key-btn') + this.submitDeleteKeyButton = page.getByTestId('submit-delete-key') + this.confirmDeleteKeyButton = page.getByTestId('delete-key-confirm-btn') + this.editKeyTTLButton = page.getByTestId('edit-ttl-btn') + this.refreshKeysButton = page.getByTestId('keys-refresh-btn') + this.refreshKeyButton = page.getByTestId('key-refresh-btn') + this.editKeyNameButton = page.getByTestId('edit-key-btn') + this.editKeyValueButton = page.getByTestId('edit-key-value-btn') + this.closeKeyButton = page.getByTestId('close-key-btn') + this.plusAddKeyButton = page.getByTestId('btn-add-key') + this.addKeyValueItemsButton = page.getByTestId('add-key-value-items-btn') + this.saveHashFieldButton = page.getByTestId('save-fields-btn') + this.saveMemberButton = page.getByTestId('save-members-btn') + this.searchButtonInKeyDetails = page.getByTestId('search-button') + this.addKeyButton = page.getByTestId('add-key-hash-btn') + this.keyTypeDropDown = page.locator('fieldset button.euiSuperSelectControl') + this.confirmRemoveHashFieldButton = page.locator('[data-testid^="remove-hash-button-"] span') + this.removeSetMemberButton = page.getByTestId('set-remove-btn') + this.removeHashFieldButton = page.getByTestId('remove-hash-button') + this.removeZsetMemberButton = page.getByTestId('zset-remove-button') + this.confirmRemoveSetMemberButton = page.locator('[data-testid^="set-remove-btn-"] span') + this.confirmRemoveZSetMemberButton = page.locator('[data-testid^="zset-remove-button-"] span') + this.saveElementButton = page.getByTestId('save-elements-btn') + this.removeElementFromListIconButton = page.getByTestId('remove-key-value-items-btn') + this.removeElementFromListButton = page.getByTestId('remove-elements-btn') + this.confirmRemoveListElementButton = page.getByTestId('remove-submit') + this.removeElementFromListSelect = page.getByTestId('destination-select') + this.addJsonObjectButton = page.getByTestId('add-object-btn') + this.addJsonFieldButton = page.getByTestId('add-field-btn') + this.expandJsonObject = page.getByTestId('expand-object') + this.scoreButton = page.getByTestId('score-button') + this.sortingButton = page.getByTestId('header-sorting-button') + this.editJsonObjectButton = page.getByTestId('edit-json-field') + this.applyEditButton = page.getByTestId('apply-edit-btn') + this.cancelEditButton = page.getByTestId('cancel-edit-btn') + this.scanMoreButton = page.getByTestId('scan-more') + this.resizeBtnKeyList = page.locator('[data-test-subj="resize-btn-keyList-keyDetails"]') + this.treeViewButton = page.getByTestId('view-type-list-btn') + this.browserViewButton = page.getByTestId('view-type-browser-btn') + this.searchButton = page.getByTestId('search-btn') + this.clearFilterButton = page.getByTestId('reset-filter-btn') + this.fullScreenModeButton = page.getByTestId('toggle-full-screen') + this.closeRightPanel = page.getByTestId('close-right-panel-btn') + this.addNewStreamEntry = page.getByTestId('add-key-value-items-btn') + this.removeEntryButton = page.locator('[data-testid^="remove-entry-button-"]') + this.confirmRemoveEntryButton = page.locator('[data-testid^="remove-entry-button-"]').filter({ hasText: 'Remove' }) + this.clearStreamEntryInputs = page.getByTestId('remove-item') + this.saveGroupsButton = page.getByTestId('save-groups-btn') + this.acknowledgeButton = page.getByTestId('acknowledge-btn') + this.confirmAcknowledgeButton = page.getByTestId('acknowledge-submit') + this.claimPendingMessageButton = page.getByTestId('claim-pending-message') + this.submitButton = page.getByTestId('btn-submit') + this.consumerDestinationSelect = page.getByTestId('destination-select') + this.removeConsumerButton = page.locator('[data-testid^="remove-consumer-button"]') + this.removeConsumerGroupButton = page.locator('[data-testid^="remove-groups-button"]') + this.optionalParametersSwitcher = page.getByTestId('optional-parameters-switcher') + this.forceClaimCheckbox = page.getByTestId('force-claim-checkbox').locator('..') + this.editStreamLastIdButton = page.getByTestId('stream-group_edit-btn') + this.saveButton = page.getByTestId('save-btn') + this.bulkActionsButton = page.getByTestId('btn-bulk-actions') + this.editHashButton = page.locator('[data-testid^="hash_edit-btn-"]') + this.editHashFieldTtlButton = page.locator('[data-testid^="hash-ttl_edit-btn-"]') + this.editZsetButton = page.locator('[data-testid^="zset_edit-btn-"]') + this.editListButton = page.locator('[data-testid^="list_edit-btn-"]') + this.cancelStreamGroupBtn = page.getByTestId('cancel-stream-groups-btn') + this.patternModeBtn = page.getByTestId('search-mode-pattern-btn') + this.redisearchModeBtn = page.getByTestId('search-mode-redisearch-btn') + this.showFilterHistoryBtn = page.getByTestId('show-suggestions-btn') + this.clearFilterHistoryBtn = page.getByTestId('clear-history-btn') + this.loadSampleDataBtn = page.getByTestId('load-sample-data-btn') + this.executeBulkKeyLoadBtn = page.getByTestId('load-sample-data-btn-confirm') + this.backToBrowserBtn = page.getByTestId('back-right-panel-btn') + this.loadAllBtn = page.getByTestId('load-all-value-btn') + this.downloadAllValueBtn = page.getByTestId('download-all-value-btn') + this.openTutorialsBtn = page.getByTestId('explore-msg-btn') + this.keyItem = page.locator('[data-testid*="node-item"][data-testid*="keys:"]') + this.columnsBtn = page.getByTestId('btn-columns-actions') + + // CONTAINERS + this.streamGroupsContainer = page.getByTestId('stream-groups-container') + this.streamConsumersContainer = page.getByTestId('stream-consumers-container') + this.breadcrumbsContainer = page.getByTestId('breadcrumbs-container') + this.virtualTableContainer = page.getByTestId('virtual-table-container') + this.streamEntriesContainer = page.getByTestId('stream-entries-container') + this.streamMessagesContainer = page.getByTestId('stream-messages-container') + this.loader = page.getByTestId('type-loading') + this.newIndexPanel = page.getByTestId('create-index-panel') + + // LINKS + this.internalLinkToWorkbench = page.getByTestId('internal-workbench-link') + this.userSurveyLink = page.getByTestId('user-survey-link') + this.redisearchFreeLink = page.getByTestId('get-started-link') + this.guideLinksBtn = page.locator('[data-testid^="guide-button-"]') + + // OPTION ELEMENTS + this.stringOption = page.locator('#string') + this.jsonOption = page.locator('#ReJSON-RL') + this.setOption = page.locator('#set') + this.zsetOption = page.locator('#zset') + this.listOption = page.locator('#list') + this.hashOption = page.locator('#hash') + this.streamOption = page.locator('#stream') + this.removeFromHeadSelection = page.locator('#HEAD') + this.filterOptionType = page.locator('[data-test-subj^="filter-option-type-"]') + this.filterByKeyTypeDropDown = page.getByTestId('select-filter-key-type') + this.filterAllKeyType = page.locator('#all') + this.consumerOption = page.getByTestId('consumer-option') + this.claimTimeOptionSelect = page.getByTestId('time-option-select') + this.relativeTimeOption = page.locator('#idle') + this.timestampOption = page.locator('#time') + this.formatSwitcher = page.getByTestId('select-format-key-value') + this.formatSwitcherIcon = page.locator('[data-testid^="key-value-formatter-option-selected"]') + this.refreshIndexButton = page.getByTestId('refresh-indexes-btn') + this.selectIndexDdn = page.locator('[data-testid="select-index-placeholder"],[data-testid="select-search-mode"]') + this.createIndexBtn = page.getByTestId('create-index-btn') + this.cancelIndexCreationBtn = page.getByTestId('create-index-cancel-btn') + this.confirmIndexCreationBtn = page.getByTestId('create-index-btn') + this.resizeTrigger = page.locator('[data-testid^="resize-trigger-"]') + this.filterHistoryOption = page.getByTestId('suggestion-item-') + this.filterHistoryItemText = page.getByTestId('suggestion-item-text') + + // TABS + this.streamTabGroups = page.getByTestId('stream-tab-Groups') + this.streamTabConsumers = page.getByTestId('stream-tab-Consumers') + this.streamTabs = page.locator('[data-test-subj="stream-tabs"]') + + // TEXT INPUTS + this.addKeyNameInput = page.getByTestId('key') + this.keyNameInput = page.getByTestId('edit-key-input') + this.keyTTLInput = page.getByTestId('ttl') + this.editKeyTTLInput = page.getByTestId('edit-ttl-input') + this.ttlText = page.getByTestId('key-ttl-text').locator('span') + this.hashFieldValueInput = page.getByTestId('field-value') + this.hashFieldNameInput = page.getByTestId('field-name') + this.hashFieldValueEditor = page.getByTestId('hash_value-editor') + this.hashTtlFieldInput = page.getByTestId('hash-ttl') + this.listKeyElementEditorInput = page.getByTestId('list_value-editor-') + this.stringKeyValueInput = page.getByTestId('string-value') + this.jsonKeyValueInput = page.locator('[data-mode-id="json"]') + this.jsonUploadInput = page.getByTestId('upload-input-file') + this.setMemberInput = page.getByTestId('member-name') + this.zsetMemberScoreInput = page.getByTestId('member-score') + this.filterByPatterSearchInput = page.getByTestId('search-key') + // this.hashFieldInput = page.getByTestId('hash-field') + // this.hashValueInput = page.getByTestId('hash-value') + // this.searchInput = page.getByTestId('search') + // this.jsonKeyInput = page.getByTestId('json-key') + // this.jsonValueInput = page.getByTestId('json-value') + // this.countInput = page.getByTestId('count-input') + // this.streamEntryId = page.getByTestId('entryId') + // this.streamField = page.getByTestId('field-name') + // this.streamValue = page.getByTestId('field-value') + // this.addAdditionalElement = page.getByTestId('add-item') + // this.streamFieldsValues = page.getByTestId('stream-entry-field-') + // this.streamEntryIDDateValue = page.locator('[data-testid^="stream-entry-"][data-testid$="date"]') + // this.groupNameInput = page.getByTestId('group-name-field') + // this.consumerIdInput = page.getByTestId('id-field') + // this.streamMinIdleTimeInput = page.getByTestId('min-idle-time') + // this.claimIdleTimeInput = page.getByTestId('time-count') + // this.claimRetryCountInput = page.getByTestId('retry-count') + // this.lastIdInput = page.getByTestId('last-id-field') + // this.inlineItemEditor = page.getByTestId('inline-item-editor') + // this.indexNameInput = page.getByTestId('index-name') + // this.prefixFieldInput = page.locator('[data-test-subj="comboBoxInput"]') + // this.indexIdentifierInput = page.getByTestId('identifier-') + + // TEXT ELEMENTS + this.keySizeDetails = page.getByTestId('key-size-text') + this.keyLengthDetails = page.getByTestId('key-length-text') + this.keyNameInTheList = this.cssSelectorKey + this.hashFieldsList = page.getByTestId('hash-field-').locator('span') + this.hashValuesList = page.getByTestId('hash_content-value-').locator('span') + this.hashField = page.getByTestId('hash-field-').first() + this.hashFieldValue = page.getByTestId('hash_content-value-') + this.setMembersList = page.getByTestId('set-member-value-') + this.zsetMembersList = page.getByTestId('zset-member-value-') + this.zsetScoresList = page.getByTestId('zset_content-value-') + this.listElementsList = page.getByTestId('list_content-value-') + this.jsonKeyValue = page.getByTestId('json-data') + this.jsonError = page.getByTestId('edit-json-error') + this.tooltip = page.locator('[role="tooltip"]') + this.dialog = page.locator('[role="dialog"]') + this.noResultsFound = page.locator('[data-test-subj="no-result-found"]') + this.noResultsFoundOnly = page.getByTestId('no-result-found-only') + this.searchAdvices = page.locator('[data-test-subj="search-advices"]') + this.keysNumberOfResults = page.getByTestId('keys-number-of-results') + this.scannedValue = page.getByTestId('keys-number-of-scanned') + this.totalKeysNumber = page.getByTestId('keys-total') + this.keyDetailsBadge = page.locator('.key-details-header .euiBadge__text') + this.modulesTypeDetails = page.getByTestId('modules-type-details') + this.filteringLabel = page.getByTestId('badge-') + this.keysSummary = page.getByTestId('keys-summary') + this.multiSearchArea = page.getByTestId('multi-search') + this.keyDetailsHeader = page.getByTestId('key-details-header') + this.keyListTable = page.getByTestId('keyList-table') + this.keyListMessage = page.getByTestId('no-result-found-msg') + this.keyDetailsTable = page.getByTestId('key-details') + this.keyNameFormDetails = page.getByTestId('key-name-text') + this.keyDetailsTTL = page.getByTestId('key-ttl-text') + this.progressLine = page.locator('div.euiProgress') + this.progressKeyList = page.getByTestId('progress-key-list') + this.jsonScalarValue = page.getByTestId('json-scalar-value') + this.noKeysToDisplayText = page.getByTestId('no-result-found-msg') + this.streamEntryDate = page.locator('[data-testid*="-date"][data-testid*="stream-entry"]') + this.streamEntryIdValue = page.locator('.streamItemId[data-testid*="stream-entry"]') + this.streamFields = page.locator('[data-test-subj="stream-entries-container"] .truncateText') + this.streamVirtualContainer = page.locator('[data-testid="virtual-grid-container"] div div').first() + this.streamEntryFields = page.getByTestId('stream-entry-field') + this.confirmationMessagePopover = page.locator('div.euiPopover__panel .euiText') + this.streamGroupId = page.locator('.streamItemId[data-testid^="stream-group-id"]').first() + this.streamGroupName = page.getByTestId('stream-group-name') + this.streamMessage = page.locator('[data-testid*="-date"][data-testid^="stream-message"]') + this.streamConsumerName = page.getByTestId('stream-consumer-') + this.consumerGroup = page.getByTestId('stream-group-') + this.entryIdInfoIcon = page.getByTestId('entry-id-info-icon') + this.entryIdError = page.getByTestId('id-error') + this.pendingCount = page.getByTestId('pending-count') + this.streamRangeBar = page.getByTestId('mock-fill-range') + this.rangeLeftTimestamp = page.getByTestId('range-left-timestamp') + this.rangeRightTimestamp = page.getByTestId('range-right-timestamp') + this.jsonValue = page.getByTestId('value-as-json') + this.stringValueAsJson = page.getByTestId('value-as-json') + + // POPUPS + this.changeValueWarning = page.getByTestId('approve-popover') + + // TABLE + this.keyListItem = page.locator('[role="rowgroup"] [role="row"]') + + // DIALOG + this.noReadySearchDialogTitle = page.getByTestId('welcome-page-title') + + // CHECKBOXES + this.showTtlCheckbox = page.getByTestId('test-check-ttl').locator('..') + this.showTtlColumnCheckbox = page.getByTestId('show-ttl').locator('..') + this.showSizeColumnCheckbox = page.getByTestId('show-key-size').locator('..') + + // UTILITY FUNCTIONS + this.getHashTtlFieldInput = (fieldName: string): Locator => + page.getByTestId(`hash-ttl_content-value-${fieldName}`) + this.getListElementInput = (count: number): Locator => + page.locator(`[data-testid*="element-${count}"]`) + this.getKeySize = (keyName: string): Locator => + page.getByTestId(`size-${keyName}`) + this.getKeyTTl = (keyName: string): Locator => + page.getByTestId(`ttl-${keyName}`) + } + + async commonAddNewKey(keyName: string, TTL?: string): Promise { + + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + if (TTL !== undefined) { + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + } + await this.keyTypeDropDown.click() + } + + async addStringKey(keyName: string, value = ' ', TTL?: string): Promise { + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.stringOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + if (TTL !== undefined) { + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + } + await this.stringKeyValueInput.click() + await this.stringKeyValueInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.addKeyButton.click() + } + + async addJsonKey(keyName: string, value: string, TTL?: string): Promise { + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.jsonOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.jsonKeyValueInput.click() + await this.jsonKeyValueInput.fill(value, { timeout: 0, noWaitAfter: false }) + if (TTL !== undefined) { + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + } + await this.addKeyButton.click() + } + + async addSetKey(keyName: string, TTL = ' ', members = ' '): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.setOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(members, { timeout: 0, noWaitAfter: false }) + await this.addKeyButton.click() + } + + async addZSetKey(keyName: string, scores = ' ', TTL = ' ', members = ' '): Promise { + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.zsetOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(members, { timeout: 0, noWaitAfter: false }) + await this.zsetMemberScoreInput.fill(scores, { timeout: 0, noWaitAfter: false }) + await this.addKeyButton.click() + } + + async addListKey(keyName: string, TTL = ' ', element: string[] = [' '], position: AddElementInList = AddElementInList.Tail): Promise { + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.listOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + if (position === AddElementInList.Head) { + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await expect(this.removeFromHeadSelection).not.toBeVisible() + } + for (let i = 0; i < element.length; i++) { + await this.getListElementInput(i).click() + await this.getListElementInput(i).fill(element[i], { timeout: 0, noWaitAfter: false }) + if (element.length > 1 && i < element.length - 1) { + await this.addAdditionalElement.click() + } + } + await this.addKeyButton.click() + } + + async addHashKey(keyName: string, TTL = ' ', field = ' ', value = ' ', fieldTtl = ''): Promise { + if (await this.toast.isCloseButtonVisible()) { + await this.toast.closeToast() + } + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.hashOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + await this.hashFieldNameInput.fill(field, { timeout: 0, noWaitAfter: false }) + await this.hashFieldValueInput.fill(value, { timeout: 0, noWaitAfter: false }) + if (fieldTtl !== '') { + await this.hashTtlFieldInput.fill(fieldTtl, { timeout: 0, noWaitAfter: false }) + } + await this.addKeyButton.click() + } + + async addStreamKey(keyName: string, field: string, value: string, TTL?: string): Promise { + await this.commonAddNewKey(keyName, TTL) + await this.streamOption.click() + await expect(this.streamEntryId).toHaveValue('*', { timeout: 5000 }) + await this.streamField.fill(field, { timeout: 0, noWaitAfter: false }) + await this.streamValue.fill(value, { timeout: 0, noWaitAfter: false }) + await expect(this.addKeyButton).not.toBeDisabled() + await this.addKeyButton.click() + await this.toast.closeToast() + } + + async addEntryToStream(field: string, value: string, entryId?: string): Promise { + await this.addNewStreamEntry.click() + await this.streamField.fill(field, { timeout: 0, noWaitAfter: false }) + await this.streamValue.fill(value, { timeout: 0, noWaitAfter: false }) + if (entryId !== undefined) { + await this.streamEntryId.fill(entryId, { timeout: 0, noWaitAfter: false }) + } + await this.saveElementButton.click() + await expect(this.streamEntriesContainer).toContainText(field) + await expect(this.streamEntriesContainer).toContainText(value) + } + + async fulfillSeveralStreamFields(fields: string[], values: string[], entryId?: string): Promise { + for (let i = 0; i < fields.length; i++) { + await this.streamField.nth(-1).fill(fields[i], { timeout: 0, noWaitAfter: false }) + await this.streamValue.nth(-1).fill(values[i], { timeout: 0, noWaitAfter: false }) + if (i < fields.length - 1) { + await this.addAdditionalElement.click() + } + } + if (entryId !== undefined) { + await this.streamEntryId.fill(entryId, { timeout: 0, noWaitAfter: false }) + } + } + + async selectFilterGroupType(groupName: string): Promise { + await this.filterByKeyTypeDropDown.click() + await this.filterOptionType.withText(groupName).click() + } + + async setAllKeyType(): Promise { + await this.filterByKeyTypeDropDown.click() + await this.filterAllKeyType.click() + } + + async searchByKeyName(keyName: string): Promise { + await this.filterByPatterSearchInput.click() + await this.filterByPatterSearchInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.page.keyboard.press('Enter') + } + + getKeySelectorByName(keyName: string): Locator { + return this.page.locator(`[data-testid="key-${keyName}"]`) + } + + async isKeyIsDisplayedInTheList(keyName: string): Promise { + const keyNameInTheList = this.getKeySelectorByName(keyName) + await this.waitForLocatorNotVisible(this.loader) + return await keyNameInTheList.isVisible() + } + + async deleteKey(): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.keyNameInTheList.click() + await this.deleteKeyButton.click() + await this.confirmDeleteKeyButton.click() + } + + async deleteKeyByName(keyName: string): Promise { + await this.searchByKeyName(keyName) + await this.keyNameInTheList.hover() + await this.keyNameInTheList.click() + await this.deleteKeyButton.click() + await this.confirmDeleteKeyButton.click() + } + + async deleteKeysByNames(keyNames: string[]): Promise { + for (const name of keyNames) { + await this.deleteKeyByName(name) + } + } + + async deleteKeyByNameFromList(keyName: string): Promise { + await this.searchByKeyName(keyName) + await this.keyNameInTheList.hover() + await this.page.locator(`[data-testid="delete-key-btn-${keyName}"]`).click() + await this.submitDeleteKeyButton.click() + } + + async editKeyName(keyName: string): Promise { + await this.editKeyNameButton.click() + await this.keyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.EditorButton.applyBtn.click() + } + + async editStringKeyValue(value: string): Promise { + await this.stringKeyValueInput.click() + await this.stringKeyValueInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.EditorButton.applyBtn.click() + } + + async getStringKeyValue(): Promise { + return await this.stringKeyValueInput.textContent() + } + + async getZsetKeyScore(): Promise { + return await this.zsetScoresList.textContent() + } + + async addFieldToHash(keyFieldValue: string, keyValue: string, fieldTtl = ''): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + await this.hashFieldInput.fill(keyFieldValue, { timeout: 0, noWaitAfter: false }) + await this.hashValueInput.fill(keyValue, { timeout: 0, noWaitAfter: false }) + if (fieldTtl !== '') { + await this.hashTtlFieldInput.fill(fieldTtl, { timeout: 0, noWaitAfter: false }) + } + await this.saveHashFieldButton.click() + } + + async editHashKeyValue(value: string): Promise { + await this.hashFieldValue.hover() + await this.editHashButton.click() + await this.hashFieldValueEditor.fill(value, { timeout: 0, noWaitAfter: false }) + await this.EditorButton.applyBtn.click() + } + + async editHashFieldTtlValue(fieldName: string, fieldTtl: string): Promise { + await this.getHashTtlFieldInput(fieldName).hover() + await this.editHashFieldTtlButton.click() + await this.inlineItemEditor.fill(fieldTtl, { timeout: 0, noWaitAfter: false }) + await this.applyButton.click() + } + + async getHashKeyValue(): Promise { + return await this.hashFieldValue.textContent() + } + + async editListKeyValue(value: string): Promise { + await this.listElementsList.hover() + await this.editListButton.click() + await this.listKeyElementEditorInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.EditorButton.applyBtn.click() + } + + async getListKeyValue(): Promise { + return await this.listElementsList.textContent() + } + + async getJsonKeyValue(): Promise { + return await this.jsonKeyValue.textContent() + } + + async searchByTheValueInKeyDetails(value: string): Promise { + await this.searchButtonInKeyDetails.click() + await this.searchInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.page.keyboard.press('Enter') + } + + async secondarySearchByTheValueInKeyDetails(value: string): Promise { + await this.searchInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.page.keyboard.press('Enter') + } + + async searchByTheValueInSetKey(value: string): Promise { + await this.searchInput.click() + await this.searchInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.page.keyboard.press('Enter') + } + + async addMemberToSet(keyMember: string): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + await this.setMemberInput.fill(keyMember, { timeout: 0, noWaitAfter: false }) + await this.saveMemberButton.click() + } + + async addMemberToZSet(keyMember: string, score: string): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + await this.setMemberInput.fill(keyMember, { timeout: 0, noWaitAfter: false }) + await this.zsetMemberScoreInput.fill(score, { timeout: 0, noWaitAfter: false }) + await this.saveMemberButton.click() + } + + async openKeyDetails(keyName: string): Promise { + await this.searchByKeyName(keyName) + await this.keyNameInTheList.click() + } + + async openKeyDetailsByKeyName(keyName: string): Promise { + const keyNameInTheList = this.page.locator(`[data-testid="key-${keyName}"]`) + await keyNameInTheList.click() + } + + async addElementToList(element: string[], position: AddElementInList = AddElementInList.Tail): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + if (position === AddElementInList.Head) { + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await expect(this.removeFromHeadSelection).not.toBeVisible() + } + for (let i = 0; i < element.length; i++) { + await this.getListElementInput(i).click() + await this.getListElementInput(i).fill(element[i], { timeout: 0, noWaitAfter: false }) + if (element.length > 1 && i < element.length - 1) { + await this.addAdditionalElement.click() + } + } + await this.addKeyButton.click() + } + + async removeListElementFromHeadOld(): Promise { + await this.removeElementFromListIconButton.click() + await expect(await this.countInput.getAttribute('disabled')).toBeTruthy() + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async removeListElementFromTail(count: string): Promise { + await this.removeElementFromListIconButton.click() + await this.countInput.fill(count, { timeout: 0, noWaitAfter: false }) + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async removeListElementFromHead(count: string): Promise { + await this.removeElementFromListIconButton.click() + await this.countInput.fill(count, { timeout: 0, noWaitAfter: false }) + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async addJsonKeyOnTheSameLevel(jsonKey: string, jsonKeyValue: string): Promise { + await this.addJsonObjectButton.click() + await this.jsonKeyInput.fill(jsonKey, { timeout: 0, noWaitAfter: false }) + await this.jsonValueInput.fill(jsonKeyValue, { timeout: 0, noWaitAfter: false }) + await this.EditorButton.applyBtn.click() + } + + async addJsonKeyInsideStructure(jsonKey: string, jsonKeyValue: string): Promise { + await this.expandJsonObject.click() + await this.addJsonFieldButton.click() + await this.jsonKeyInput.fill(jsonKey, { timeout: 0, noWaitAfter: false }) + await this.jsonValueInput.fill(jsonKeyValue, { timeout: 0, noWaitAfter: false }) + await this.EditorButton.applyBtn.click() + } + + async addJsonValueInsideStructure(jsonKeyValue: string): Promise { + await this.expandJsonObject.click() + await this.addJsonFieldButton.click() + await this.jsonValueInput.fill(jsonKeyValue, { timeout: 0, noWaitAfter: false }) + await this.applyButton.click() + } + + async addJsonStructure(jsonStructure: string): Promise { + if (await this.expandJsonObject.isVisible()) { + await this.expandJsonObject.click() + } + await this.editJsonObjectButton.click() + await this.jsonValueInput.fill(jsonStructure, { timeout: 0, noWaitAfter: false }) + await this.applyEditButton.click() + } + + async deleteStreamEntry(): Promise { + await this.removeEntryButton.click() + await this.confirmRemoveEntryButton.click() + } + + async getKeyLength(): Promise { + const rawValue = await this.keyLengthDetails.textContent() + const parts = rawValue.split(' ') + return parts[parts.length - 1] + } + + async createConsumerGroup(groupName: string, id?: string): Promise { + await this.addKeyValueItemsButton.click() + await this.groupNameInput.fill(groupName, { timeout: 0, noWaitAfter: false }) + if (id !== undefined) { + await this.consumerIdInput.fill(id, { timeout: 0, noWaitAfter: false }) + } + await this.saveGroupsButton.click() + } + + async openStreamPendingsView(keyName: string): Promise { + await this.openKeyDetails(keyName) + await this.streamTabGroups.click() + await this.consumerGroup.click() + await this.streamConsumerName.click() + } + + async selectFormatter(formatter: string): Promise { + const option = this.page.locator(`[data-test-subj="format-option-${formatter}"]`) + await this.formatSwitcher.click() + await option.click() + } + + async verifyScannningMore(): Promise { + for (let i = 10; i < 100; i += 10) { + const rememberedScanResults = Number((await this.keysNumberOfResults.textContent()).replace(/\s/g, '')) + await expect(this.progressKeyList).not.toBeVisible({ timeout: 30000 }) + const scannedValueText = await this.scannedValue.textContent() + const regExp = new RegExp(`${i} ...`) + await expect(scannedValueText).toMatch(regExp) + await this.scanMoreButton.click() + const scannedResults = Number((await this.keysNumberOfResults.textContent()).replace(/\s/g, '')) + await expect(scannedResults).toBeGreaterThan(rememberedScanResults) + } + } + + async selectIndexByName(index: string): Promise { + const option = this.page.locator(`[data-test-subj="mode-option-type-${index}"]`) + await this.selectIndexDdn.click() + await option.click() + } + + async verifyNoKeysInDatabase(): Promise { + await expect(this.keyListMessage).toBeVisible() + await expect(this.keysSummary).not.toBeVisible() + } + + async clearFilter(): Promise { + await this.clearFilterButton.click() + } + + async clickGuideLinksByName(guide: string): Promise { + const linkGuide = this.page.locator('[data-testid^="guide-button-"]').withText(guide) + await linkGuide.click() + } +} diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 5a7954277b..cfbb9b0434 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,19 +1,29 @@ -import { test, expect } from '../fixtures/open-ri' +import {test, expect} from '../fixtures/open-ri' +import {Common} from '../helpers/common' +import {BrowserPage} from "../pageObjects/browser-page"; -test.beforeEach(async ({ basePage , dialogUserAgreement}) => { +let keyName: string +let browserPage: BrowserPage +test.beforeEach(async ({page}) => { // await dialogUserAgreement.acceptLicenseTerms() // await basePage.getText('sa') - console.log("WE ARE IN THE BEFORE STEP") + console.log('WE ARE IN THE BEFORE STEP') + keyName = Common.generateWord(10) + browserPage = new BrowserPage(page) }) -test.afterEach(async ({ basePage , dialogUserAgreement}) => { +test.afterEach(async ({basePage, dialogUserAgreement}) => { // await dialogUserAgreement.acceptLicenseTerms() // await basePage.getText('sa') console.log('WE ARE IN THE AFTER STEP') + + + }) -test('basic test', async ({ basePage, page }) => { +test('basic test', async ({basePage, page}) => { // await basePage.click('button') // await expect(page.getByTestId('todo-title')).toContainText(['something nice']) console.log('WE ARE IN TEST') + await browserPage.addHashKey(keyName) }) From 9f9eef55fc16196772ee61801ee88fc277fcd3c6 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 00:24:15 +0200 Subject: [PATCH 017/128] wip - additional methods --- tests/playwright/fixtures/open-ri.ts | 22 +++++++++---------- tests/playwright/pageObjects/base-page.ts | 4 ++-- .../pageObjects/components/common/toast.ts | 5 ++++- .../pageObjects/user-agreement-dialog.ts | 4 ++++ tests/playwright/tests/example.spec.ts | 11 +++++++--- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 62efdad43a..286e52e386 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -78,13 +78,13 @@ // // export { expect } from '@playwright/test' -import { test as base } from '@playwright/test' +import { test as base, Page } from '@playwright/test' import BasePage from '../pageObjects/base-page' import { UserAgreementDialog } from '../pageObjects/user-agreement-dialog' import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { ossStandaloneConfig } from '../helpers/conf' import { MyRedisDatabasePage } from '../pageObjects/my-redis-databases-page' -import { APIKeyRequests } from '../helpers/api/api-keys' +// import { APIKeyRequests } from '../helpers/api/api-keys' // Define shared worker object type WorkerSharedState = { @@ -95,7 +95,7 @@ type WorkerSharedState = { // Define test fixture types type OpenRedisInsight = { - basePage: BasePage; + basePage: Page; dialogUserAgreement: UserAgreementDialog; workerState: WorkerSharedState; // Worker-scoped object passed to tests } @@ -154,16 +154,16 @@ export const test = base.extend< const myDbPage = new MyRedisDatabasePage(page) await myDbPage.clickOnDBByName(workerState.dbConfig.databaseName) - await use(basePage) - }, - - // ✅ Test-scoped `dialogUserAgreement` - dialogUserAgreement: async ({ page }, use) => { - console.log('Fixture setup: Accepting User Agreement') const userAgreementDialog = new UserAgreementDialog(page) - await userAgreementDialog.acceptLicenseTerms() - await use(userAgreementDialog) + if(await userAgreementDialog.isUserAgreementDialogVisible()){ + await userAgreementDialog.acceptLicenseTerms() + await use(userAgreementDialog) + } + + await use(page) }, + + }) export { expect } from '@playwright/test' diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 958b00f0f0..5f7cbb6e39 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -27,8 +27,8 @@ export default class BasePage { await this.page.fill(selector, value) } - async getText(selector: string): Promise { - return await this.page.textContent(selector) || '' + async getText(locator: Locator): Promise { + return locator.textContent() } async isVisible(locator: Locator): Promise { diff --git a/tests/playwright/pageObjects/components/common/toast.ts b/tests/playwright/pageObjects/components/common/toast.ts index c150c522e4..9c1f5b9d37 100644 --- a/tests/playwright/pageObjects/components/common/toast.ts +++ b/tests/playwright/pageObjects/components/common/toast.ts @@ -1,4 +1,4 @@ -import { Locator, Page } from '@playwright/test' +import {Locator, Page} from '@playwright/test' import BasePage from '../../base-page' export class Toast extends BasePage{ @@ -30,4 +30,7 @@ export class Toast extends BasePage{ await this.toastCloseButton.click() } + async getNotificationMessage(): Promise { + return this.toastHeader.textContent() + } } diff --git a/tests/playwright/pageObjects/user-agreement-dialog.ts b/tests/playwright/pageObjects/user-agreement-dialog.ts index f0e9008936..3fd59030c8 100644 --- a/tests/playwright/pageObjects/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/user-agreement-dialog.ts @@ -33,4 +33,8 @@ export class UserAgreementDialog extends BasePage { async getRecommendedSwitcherValue(): Promise { return this.recommendedSwitcher.getAttribute('aria-checked') } + + async isUserAgreementDialogVisible(): Promise { + return this.isVisible(this.userAgreementsPopup) + } } diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index cfbb9b0434..10c00f3e24 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,18 +1,22 @@ import {test, expect} from '../fixtures/open-ri' import {Common} from '../helpers/common' -import {BrowserPage} from "../pageObjects/browser-page"; +import {BrowserPage} from '../pageObjects/browser-page' +import {Toast} from '../pageObjects/components/common/toast' let keyName: string let browserPage: BrowserPage +let toast: Toast + test.beforeEach(async ({page}) => { // await dialogUserAgreement.acceptLicenseTerms() // await basePage.getText('sa') console.log('WE ARE IN THE BEFORE STEP') keyName = Common.generateWord(10) browserPage = new BrowserPage(page) + toast = new Toast(page) }) -test.afterEach(async ({basePage, dialogUserAgreement}) => { +test.afterEach(async ({basePage}) => { // await dialogUserAgreement.acceptLicenseTerms() // await basePage.getText('sa') console.log('WE ARE IN THE AFTER STEP') @@ -21,9 +25,10 @@ test.afterEach(async ({basePage, dialogUserAgreement}) => { }) -test('basic test', async ({basePage, page}) => { +test('basic test', async ({ page}) => { // await basePage.click('button') // await expect(page.getByTestId('todo-title')).toContainText(['something nice']) console.log('WE ARE IN TEST') await browserPage.addHashKey(keyName) + await expect(toast.getNotificationMessage()).toContain('Key has been added') }) From 0c7e1315b0db59c3661d735002ff5f9eb8995fc9 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 11:44:57 +0200 Subject: [PATCH 018/128] added selector module --- tests/playwright/pageObjects/base-page.ts | 4 ++-- .../pageObjects/components/common/toast.ts | 18 ++++++++++-------- tests/playwright/selectors/index.ts | 1 + tests/playwright/selectors/toast-selectors.js | 9 +++++++++ tests/playwright/tests/example.spec.ts | 19 ++++++++----------- 5 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 tests/playwright/selectors/index.ts create mode 100644 tests/playwright/selectors/toast-selectors.js diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 5f7cbb6e39..6c694005e2 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -31,8 +31,8 @@ export default class BasePage { return locator.textContent() } - async isVisible(locator: Locator): Promise { - return locator.isVisible() + async isVisible(selctor: string): Promise { + return this.page.locator(selctor).isVisible() } async getByTestId(testId: string): Promise { diff --git a/tests/playwright/pageObjects/components/common/toast.ts b/tests/playwright/pageObjects/components/common/toast.ts index 9c1f5b9d37..6dd3640891 100644 --- a/tests/playwright/pageObjects/components/common/toast.ts +++ b/tests/playwright/pageObjects/components/common/toast.ts @@ -1,7 +1,9 @@ import {Locator, Page} from '@playwright/test' import BasePage from '../../base-page' +import {ToastSelectors} from '../../../selectors' export class Toast extends BasePage{ + private readonly toastHeader: Locator private readonly toastBody: Locator private readonly toastSuccess: Locator @@ -12,18 +14,18 @@ export class Toast extends BasePage{ constructor(page: Page) { super(page) - this.toastHeader = page.getByTestId('euiToastHeader') - this.toastBody = page.locator('[class*=euiToastBody]') - this.toastSuccess = page.locator('[class*=euiToast--success]') - this.toastError = page.locator('[class*=euiToast--danger]') - this.toastCloseButton = page.getByTestId('toastCloseButton') - this.toastSubmitBtn = page.getByTestId('submit-tooltip-btn') - this.toastCancelBtn = page.getByTestId('toast-cancel-btn') + this.toastHeader = page.locator(ToastSelectors.toastHeader) + this.toastBody = page.locator(ToastSelectors.toastBody) + this.toastSuccess = page.locator(ToastSelectors.toastSuccess) + this.toastError = page.locator(ToastSelectors.toastError) + this.toastCloseButton = page.locator(ToastSelectors.toastCloseButton) + this.toastSubmitBtn = page.getByTestId(ToastSelectors.toastSubmitBtn) + this.toastCancelBtn = page.getByTestId(ToastSelectors.toastCancelBtn) } async isCloseButtonVisible(): Promise { - return this.isVisible(this.toastCloseButton) + return this.isVisible(ToastSelectors.toastCloseButton) } async closeToast(): Promise { diff --git a/tests/playwright/selectors/index.ts b/tests/playwright/selectors/index.ts new file mode 100644 index 0000000000..d9facc4343 --- /dev/null +++ b/tests/playwright/selectors/index.ts @@ -0,0 +1 @@ +export * from './toast-selectors' diff --git a/tests/playwright/selectors/toast-selectors.js b/tests/playwright/selectors/toast-selectors.js new file mode 100644 index 0000000000..b635b412a9 --- /dev/null +++ b/tests/playwright/selectors/toast-selectors.js @@ -0,0 +1,9 @@ +export const ToastSelectors: Record = { + toastHeader: '[data-test-subj=euiToastHeader]', + toastBody: '[class*=euiToastBody]', + toastSuccess: '[class*=euiToast--success]', + toastError: '[class*=euiToast--danger]', + toastCloseButton: '[data-test-subj=toastCloseButton]', + toastSubmitBtn: 'submit-tooltip-btn', + toastCancelBtn: 'toast-cancel-btn' +}; diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 10c00f3e24..f1bdc7529e 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -5,29 +5,26 @@ import {Toast} from '../pageObjects/components/common/toast' let keyName: string let browserPage: BrowserPage -let toast: Toast -test.beforeEach(async ({page}) => { - // await dialogUserAgreement.acceptLicenseTerms() - // await basePage.getText('sa') + +test.beforeEach(async ({basePage}) => { + console.log('WE ARE IN THE BEFORE STEP') keyName = Common.generateWord(10) - browserPage = new BrowserPage(page) - toast = new Toast(page) + browserPage = new BrowserPage(basePage) + }) test.afterEach(async ({basePage}) => { - // await dialogUserAgreement.acceptLicenseTerms() - // await basePage.getText('sa') + console.log('WE ARE IN THE AFTER STEP') }) -test('basic test', async ({ page}) => { - // await basePage.click('button') - // await expect(page.getByTestId('todo-title')).toContainText(['something nice']) +test('basic test', async ({ basePage}) => { + console.log('WE ARE IN TEST') await browserPage.addHashKey(keyName) await expect(toast.getNotificationMessage()).toContain('Key has been added') From 43d6a27b7eada8e526dde5457d0e02443a348f7f Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 12:09:39 +0200 Subject: [PATCH 019/128] more selectors added --- tests/playwright/fixtures/open-ri.ts | 2 +- .../{ => dialogs}/user-agreement-dialog.ts | 17 +++++++++-------- tests/playwright/selectors/index.ts | 1 + tests/playwright/selectors/toast-selectors.js | 2 +- .../selectors/user-agreement-selectors.ts | 8 ++++++++ 5 files changed, 20 insertions(+), 10 deletions(-) rename tests/playwright/pageObjects/{ => dialogs}/user-agreement-dialog.ts (58%) create mode 100644 tests/playwright/selectors/user-agreement-selectors.ts diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 286e52e386..0861630e64 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -80,7 +80,7 @@ import { test as base, Page } from '@playwright/test' import BasePage from '../pageObjects/base-page' -import { UserAgreementDialog } from '../pageObjects/user-agreement-dialog' +import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialog' import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { ossStandaloneConfig } from '../helpers/conf' import { MyRedisDatabasePage } from '../pageObjects/my-redis-databases-page' diff --git a/tests/playwright/pageObjects/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts similarity index 58% rename from tests/playwright/pageObjects/user-agreement-dialog.ts rename to tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index 3fd59030c8..d5d2ef9d58 100644 --- a/tests/playwright/pageObjects/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -1,5 +1,6 @@ import { expect, Locator, Page } from '@playwright/test' -import BasePage from './base-page' +import BasePage from '../base-page' +import { UserAgreementSelectors } from '../../selectors' export class UserAgreementDialog extends BasePage { @@ -13,12 +14,12 @@ export class UserAgreementDialog extends BasePage { constructor(page: Page) { super(page) - this.userAgreementsPopup = this.getByTestId('consents-settings-popup') - this.submitButton = this.getByTestId('btn-submit') - this.switchOptionEula = this.getByTestId('switch-option-eula') - this.switchOptionEncryption = this.getByTestId('switch-option-encryption') - this.pluginSectionWithText = this.getByTestId('plugin-section') - this.recommendedSwitcher = this.getByTestId('switch-option-recommended') + this.userAgreementsPopup = page.getByTestId(UserAgreementSelectors.userAgreementsPopup) + this.submitButton = page.getByTestId(UserAgreementSelectors.submitButton) + this.switchOptionEula = page.getByTestId(UserAgreementSelectors.switchOptionEula) + this.switchOptionEncryption = page.getByTestId(UserAgreementSelectors.switchOptionEncryption) + this.pluginSectionWithText = page.getByTestId(UserAgreementSelectors.pluginSectionWithText) + this.recommendedSwitcher = page.getByTestId(UserAgreementSelectors.recommendedSwitcher) } async acceptLicenseTerms(): Promise { @@ -35,6 +36,6 @@ export class UserAgreementDialog extends BasePage { } async isUserAgreementDialogVisible(): Promise { - return this.isVisible(this.userAgreementsPopup) + return this.isVisible(UserAgreementSelectors.userAgreementsPopup) } } diff --git a/tests/playwright/selectors/index.ts b/tests/playwright/selectors/index.ts index d9facc4343..a9d404ae29 100644 --- a/tests/playwright/selectors/index.ts +++ b/tests/playwright/selectors/index.ts @@ -1 +1,2 @@ export * from './toast-selectors' +export * from './user-agreement-selectors' diff --git a/tests/playwright/selectors/toast-selectors.js b/tests/playwright/selectors/toast-selectors.js index b635b412a9..1b0e794cd3 100644 --- a/tests/playwright/selectors/toast-selectors.js +++ b/tests/playwright/selectors/toast-selectors.js @@ -1,4 +1,4 @@ -export const ToastSelectors: Record = { +export const ToastSelectors = { toastHeader: '[data-test-subj=euiToastHeader]', toastBody: '[class*=euiToastBody]', toastSuccess: '[class*=euiToast--success]', diff --git a/tests/playwright/selectors/user-agreement-selectors.ts b/tests/playwright/selectors/user-agreement-selectors.ts new file mode 100644 index 0000000000..f3d7b61810 --- /dev/null +++ b/tests/playwright/selectors/user-agreement-selectors.ts @@ -0,0 +1,8 @@ +export const UserAgreementSelectors = { + userAgreementsPopup: 'consents-settings-popup', + submitButton: 'btn-submit', + switchOptionEula: 'switch-option-eula', + switchOptionEncryption: 'switch-option-encryption', + pluginSectionWithText: 'plugin-section', + recommendedSwitcher: 'switch-option-recommended' +}; From e838356ad43434756dccffa7f29a3a3dd159425d Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 12:15:40 +0200 Subject: [PATCH 020/128] pass the notification check --- tests/playwright/tests/example.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index f1bdc7529e..09c75f8c05 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -27,5 +27,8 @@ test('basic test', async ({ basePage}) => { console.log('WE ARE IN TEST') await browserPage.addHashKey(keyName) - await expect(toast.getNotificationMessage()).toContain('Key has been added') + // checks that the notification is displayed + await expect(await basePage.getByText('Key has been added')).toBeVisible() + + }) From c1b75e8b45e064e9b6030ec949b04907ec6c67d3 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 12:19:44 +0200 Subject: [PATCH 021/128] full test passing db delete call failing --- tests/playwright/tests/example.spec.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 09c75f8c05..2b18faf71e 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -27,8 +27,16 @@ test('basic test', async ({ basePage}) => { console.log('WE ARE IN TEST') await browserPage.addHashKey(keyName) - // checks that the notification is displayed + + // checks that the notification is displayed (should be in a different test) await expect(await basePage.getByText('Key has been added')).toBeVisible() + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + + + }) From cc51edc659b25a3e4631fb6fedcda90e13dd72d6 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 12:36:30 +0200 Subject: [PATCH 022/128] additional generative methods --- tests/playwright/fixtures/open-ri.ts | 5 +++-- tests/playwright/helpers/common.ts | 31 +++++++++++++++++--------- tests/playwright/tests/example.spec.ts | 7 +----- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 0861630e64..b0ccfac558 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -133,13 +133,14 @@ export const test = base.extend< await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) await use() // Run the tests - + // Something failing here doesn't affect test execution result console.log(`Stopping test worker ${ti}`) // Cleanup after all tests in this worker // const apiKeyClient = new APIKeyRequests(workerState.apiUrl) // await apiKeyClient.deleteKeyByNameApi(); - await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + // throw new Error("test worker error") + // await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) }, { scope: 'worker', auto: true }], // ✅ Test-scoped `basePage` using worker state diff --git a/tests/playwright/helpers/common.ts b/tests/playwright/helpers/common.ts index 90c8c8493a..2502524d08 100644 --- a/tests/playwright/helpers/common.ts +++ b/tests/playwright/helpers/common.ts @@ -148,21 +148,30 @@ export class Common { // return element.getStyleProperty('background-color') // } // -// /** -// * Generate word by number of symbols -// * @param number The number of symbols -// */ + /** + * Generate word by number of symbols + * @param number The number of symbols + */ static generateWord(number: number): string { return faker.word.sample({ length: number }) } - // /** - // * Generate sentence by number of words - // * @param number The number of words - // */ - // static generateSentence(number: number): string { - // return chance.sentence({ words: number }) - // } + /** + * Generate sentence by number of words + * @param number The number of words + */ + static generateSentence(number: number): string { + return faker.lorem.sentence( number) + } + + /** + * Generate sentence by number of characters + * @param number The number of characters + */ + static generateAlpanumeric(number: number): string { + return faker.string.alphanumeric(number) + } + // // /** // * Return api endpoint with disabled certificate validation diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 2b18faf71e..55cdb79d0f 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,26 +1,21 @@ import {test, expect} from '../fixtures/open-ri' import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' -import {Toast} from '../pageObjects/components/common/toast' let keyName: string let browserPage: BrowserPage - test.beforeEach(async ({basePage}) => { console.log('WE ARE IN THE BEFORE STEP') - keyName = Common.generateWord(10) + keyName = Common.generateAlpanumeric(10) browserPage = new BrowserPage(basePage) }) test.afterEach(async ({basePage}) => { - console.log('WE ARE IN THE AFTER STEP') - - }) test('basic test', async ({ basePage}) => { From f806ce301abc591e3e25ad38a3d49daff2f31202 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 13:06:03 +0200 Subject: [PATCH 023/128] wip delete api --- tests/playwright/fixtures/open-ri.ts | 7 ++++--- tests/playwright/tests/example.spec.ts | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index b0ccfac558..38a0ecf4aa 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -84,6 +84,7 @@ import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialo import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { ossStandaloneConfig } from '../helpers/conf' import { MyRedisDatabasePage } from '../pageObjects/my-redis-databases-page' +import {APIKeyRequests} from "../helpers/api/api-keys"; // import { APIKeyRequests } from '../helpers/api/api-keys' // Define shared worker object @@ -137,10 +138,10 @@ export const test = base.extend< console.log(`Stopping test worker ${ti}`) // Cleanup after all tests in this worker - // const apiKeyClient = new APIKeyRequests(workerState.apiUrl) - // await apiKeyClient.deleteKeyByNameApi(); + // throw new Error("test worker error") - // await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) }, { scope: 'worker', auto: true }], // ✅ Test-scoped `basePage` using worker state diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 55cdb79d0f..1291239b61 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,6 +1,7 @@ import {test, expect} from '../fixtures/open-ri' import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' +import {APIKeyRequests} from "../helpers/api/api-keys"; let keyName: string let browserPage: BrowserPage @@ -13,8 +14,10 @@ test.beforeEach(async ({basePage}) => { }) -test.afterEach(async ({basePage}) => { +test.afterEach(async ({basePage, workerState }) => { console.log('WE ARE IN THE AFTER STEP') + const apiKeyClient = new APIKeyRequests(workerState.apiUrl) + await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) }) From f8a7603aaa22e0b2601ce1267a31ee790861476b Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 14:20:13 +0200 Subject: [PATCH 024/128] change url to original --- tests/playwright/helpers/api/api-keys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index b39c32cfd3..a6c97b29f0 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -133,6 +133,6 @@ export class APIKeyRequests { async deleteKeyByNameApi(keyName: string, databaseName: string): Promise { const databaseId = await this.getDatabaseId(databaseName) const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } - await this.httpClient.delete(`/databases/${databaseId}/keys`, requestBody) + await this.httpClient.delete(`/databases/${databaseId}/keys?encoding=buffer`, requestBody) } } From ec2bca4c3f10d4aa50fa41ae65c1519ebf36d5eb Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 15:21:26 +0200 Subject: [PATCH 025/128] test passing again with debug axios impl --- tests/playwright/helpers/api/api-keys.ts | 276 +++++++++++++++-------- 1 file changed, 184 insertions(+), 92 deletions(-) diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index a6c97b29f0..fb4a5be276 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -1,4 +1,5 @@ -import { HttpClient } from './http-client' +import axios from 'axios' +// import { HttpClient } from './http-client' import { DatabaseAPIRequests } from './api-databases' import { AddNewDatabaseParameters, @@ -10,98 +11,164 @@ import { StreamKeyParameters} from '../../types' -const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' +// const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' +// +// export class APIKeyRequests { +// private httpClient: HttpClient +// +// private databaseAPIRequests : DatabaseAPIRequests +// +// constructor(baseURL: string) { +// this.httpClient = new HttpClient(baseURL) +// this.databaseAPIRequests = new DatabaseAPIRequests(baseURL) +// } +// +// private async getDatabaseId(databaseName: string): Promise { +// const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) +// if (!databaseId) throw new Error(`Database with name ${databaseName} not found`) +// return databaseId +// } +// +// // async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// // const requestBody = { +// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// // fields: keyParameters.fields.map(fields => ({ +// // ...fields, +// // field: Buffer.from(fields.field, 'utf-8'), +// // value: Buffer.from(fields.value, 'utf-8') +// // })) +// // } +// // +// // await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) +// // } +// // +// // async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// // const requestBody = { +// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// // entries: keyParameters.entries.map(member => ({ +// // ...member, +// // fields: member.fields.map(({ name, value }) => ({ +// // name: Buffer.from(name, 'utf-8'), +// // value: Buffer.from(value, 'utf-8') +// // })) +// // })) +// // } +// // +// // await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) +// // } +// // +// // async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// // const requestBody = { +// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// // members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) +// // } +// // +// // await this.httpClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) +// // } +// // +// // async addSortedSetKeyApi(keyParameters: SortedSetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// // const requestBody = { +// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// // members: keyParameters.members.map(member => ({ ...member, name: Buffer.from(member.name, 'utf-8') })) +// // } +// // +// // await this.httpClient.post(`/databases/${databaseId}/zSet?encoding=buffer`, requestBody) +// // } +// // +// // async addListKeyApi(keyParameters: ListKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// // const requestBody = { +// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// // element: Buffer.from(keyParameters.element, 'utf-8') +// // } +// // +// // await this.httpClient.post(`/databases/${databaseId}/list?encoding=buffer`, requestBody) +// // } +// // +// // async addStringKeyApi(keyParameters: StringKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// // const requestBody = { +// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// // value: Buffer.from(keyParameters.value, 'utf-8') +// // } +// // +// // await this.httpClient.post(`/databases/${databaseId}/string?encoding=buffer`, requestBody) +// // } +// +// +// +// async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// const requestBody = { +// keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// fields: keyParameters.fields.map(fields => ({ +// ...fields, +// field: Buffer.from(fields.field, 'utf-8'), +// value: Buffer.from(fields.value, 'utf-8') +// })) +// } +// +// await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) +// } +// +// async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { +// const databaseId = await this.getDatabaseId(databaseParameters.databaseName) +// const requestBody = { +// keyName: Buffer.from(keyParameters.keyName, 'utf-8'), +// entries: keyParameters.entries.map(member => ({ +// ...member, +// fields: member.fields.map(({ name, value }) => ({ +// name: Buffer.from(name, 'utf-8'), +// value: Buffer.from(value, 'utf-8') +// })) +// })) +// } +// +// await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) +// } +// +// async deleteKeyByNameApi(keyName: string, databaseName: string): Promise { +// const databaseId = await this.getDatabaseId(databaseName) +// // const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } +// const requestBody = { keyNames: keyName } +// await this.httpClient.delete(`/databases/${databaseId}/keys?encoding=buffer`, requestBody) +// } +// } -export class APIKeyRequests { - private httpClient: HttpClient - private databaseAPIRequests : DatabaseAPIRequests +const apiUrl = 'https://localhost:5540/api' +const databaseAPIRequests = new DatabaseAPIRequests(apiUrl) - constructor(baseURL: string) { - this.httpClient = new HttpClient(baseURL) - this.databaseAPIRequests = new DatabaseAPIRequests(baseURL) - } - - private async getDatabaseId(databaseName: string): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) - if (!databaseId) throw new Error(`Database with name ${databaseName} not found`) - return databaseId - } +const apiClient = axios.create({ + baseURL: apiUrl, + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false // Allows self-signed/invalid SSL certs + }) +}) - // async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - // const requestBody = { - // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - // fields: keyParameters.fields.map(fields => ({ - // ...fields, - // field: Buffer.from(fields.field, 'utf-8'), - // value: Buffer.from(fields.value, 'utf-8') - // })) - // } - // - // await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) - // } - // - // async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - // const requestBody = { - // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - // entries: keyParameters.entries.map(member => ({ - // ...member, - // fields: member.fields.map(({ name, value }) => ({ - // name: Buffer.from(name, 'utf-8'), - // value: Buffer.from(value, 'utf-8') - // })) - // })) - // } - // - // await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) - // } - // - // async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - // const requestBody = { - // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - // members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) - // } - // - // await this.httpClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) - // } - // - // async addSortedSetKeyApi(keyParameters: SortedSetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - // const requestBody = { - // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - // members: keyParameters.members.map(member => ({ ...member, name: Buffer.from(member.name, 'utf-8') })) - // } - // - // await this.httpClient.post(`/databases/${databaseId}/zSet?encoding=buffer`, requestBody) - // } - // - // async addListKeyApi(keyParameters: ListKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - // const requestBody = { - // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - // element: Buffer.from(keyParameters.element, 'utf-8') - // } - // - // await this.httpClient.post(`/databases/${databaseId}/list?encoding=buffer`, requestBody) - // } - // - // async addStringKeyApi(keyParameters: StringKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) - // const requestBody = { - // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - // value: Buffer.from(keyParameters.value, 'utf-8') - // } - // - // await this.httpClient.post(`/databases/${databaseId}/string?encoding=buffer`, requestBody) - // } +// Logging request and response +apiClient.interceptors.request.use(request => { + console.log('Starting Request', request) + return request +}) +apiClient.interceptors.response.use(response => { + console.log('Response:', response) + return response +}, error => { + console.error('Error Response:', error.response) + return Promise.reject(error) +}) +const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' +export class APIKeyRequests { async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), fields: keyParameters.fields.map(fields => ({ @@ -110,12 +177,12 @@ export class APIKeyRequests { value: Buffer.from(fields.value, 'utf-8') })) } - - await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) + const response = await apiClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) + if (response.status !== 201) throw new Error('The creation of new Hash key request failed') } async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.getDatabaseId(databaseParameters.databaseName) + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), entries: keyParameters.entries.map(member => ({ @@ -126,13 +193,38 @@ export class APIKeyRequests { })) })) } + const response = await apiClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) + if (response.status !== 201) throw new Error('The creation of new Stream key request failed') + } - await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) + async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) + } + const response = await apiClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) + if (response.status !== 201) throw new Error('The creation of new Set key request failed') + } + + async searchKeyByNameApi(keyName: string, databaseName: string): Promise { + const requestBody = { + cursor: '0', + match: keyName + } + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const response = await apiClient.post(bufferPathMask.replace('databaseId', databaseId), requestBody) + if (response.status !== 200) throw new Error('Getting key request failed') + return response.data[0].keys } async deleteKeyByNameApi(keyName: string, databaseName: string): Promise { - const databaseId = await this.getDatabaseId(databaseName) - const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } - await this.httpClient.delete(`/databases/${databaseId}/keys?encoding=buffer`, requestBody) + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const doesKeyExist = await this.searchKeyByNameApi(keyName, databaseName) + if (doesKeyExist.length > 0) { + const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } + const response = await apiClient.delete(bufferPathMask.replace('databaseId', databaseId), { data: requestBody }) + if (response.status !== 200) throw new Error('The deletion of the key request failed') + } } } From a8415380f7bfb997c6eab2afb9880ece07eb9b8d Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 16:03:46 +0200 Subject: [PATCH 026/128] all parts passing with debug files --- tests/playwright/fixtures/open-ri.ts | 4 +- tests/playwright/helpers/api/api-databases.ts | 211 +++++++++++++----- tests/playwright/helpers/async-helper.ts | 28 +++ 3 files changed, 186 insertions(+), 57 deletions(-) create mode 100644 tests/playwright/helpers/async-helper.ts diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 38a0ecf4aa..43d86ec98a 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -141,7 +141,7 @@ export const test = base.extend< // throw new Error("test worker error") await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) - await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + }, { scope: 'worker', auto: true }], // ✅ Test-scoped `basePage` using worker state @@ -152,7 +152,7 @@ export const test = base.extend< const basePage = new BasePage(page) await basePage.navigateToHomeUrl(workerState.baseUrl) - // Interact with the database UI + // Enter DB const myDbPage = new MyRedisDatabasePage(page) await myDbPage.clickOnDBByName(workerState.dbConfig.databaseName) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 740dc4a653..50e0a59df0 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -71,40 +71,167 @@ // } // } import { faker } from '@faker-js/faker' +import axios from 'axios' +import { Chance } from 'chance' import { HttpClient } from './http-client' import { AddNewDatabaseParameters, OSSClusterParameters, ClusterNodes, databaseParameters } from '../../types' import { ResourcePath } from '../constants' -export class DatabaseAPIRequests { - private httpClient: HttpClient +// export class DatabaseAPIRequests { +// private httpClient: HttpClient +// +// constructor(baseURL: string) { +// this.httpClient = new HttpClient(baseURL) +// } +// +// async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false): Promise { +// const uniqueId = faker.string.alphanumeric({ length: 10 }) +// const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) +// +// const requestBody: any = { +// name: databaseParameters.databaseName, +// host: databaseParameters.host, +// port: Number(databaseParameters.port), +// username: databaseParameters.databaseUsername, +// password: databaseParameters.databasePassword, +// } +// +// if (databaseParameters.caCert && databaseParameters.clientCert) { +// requestBody.tls = true +// requestBody.verifyServerCert = false +// requestBody.caCert = { +// name: `ca-${uniqueId}`, +// certificate: databaseParameters.caCert.certificate, +// } +// requestBody.clientCert = { +// name: `client-${uniqueId}`, +// certificate: databaseParameters.clientCert.certificate, +// key: databaseParameters.clientCert.key, +// } +// } +// +// if (isCloud) { +// requestBody.cloudDetails = { +// cloudId: uniqueIdNumber, +// subscriptionType: 'fixed', +// planMemoryLimit: 100, +// memoryLimitMeasurementUnit: 'mb', +// free: true, +// } +// } +// +// const response = await this.httpClient.post(ResourcePath.Databases, requestBody) +// +// if (!response || Object.keys(response).length === 0) { +// throw new Error('The response body is empty') +// } +// +// if (response.name !== databaseParameters.databaseName) { +// throw new Error(`Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}`) +// } +// } +// +// async getAllDatabases(): Promise { +// return this.httpClient.get(ResourcePath.Databases) +// } +// +// async getDatabaseIdByName(databaseName?: string): Promise { +// if (!databaseName) throw new Error('Error: Missing databaseName') +// +// const allDatabases = await this.getAllDatabases() +// const db = allDatabases.find(db => db.name === databaseName) +// if (!db) throw new Error(`Database ${databaseName} not found`) +// +// return db.id +// } +// +// async deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise { +// const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName) +// if (!databaseId) throw new Error('Error: Missing databaseId') +// +// await this.httpClient.delete(ResourcePath.Databases, { ids: [`${databaseId}`] }) +// } +// +// async deleteAllDatabasesApi(): Promise { +// const allDatabases = await this.getAllDatabases() +// const databaseIds = allDatabases.map(db => db.id) +// +// if (databaseIds.length) { +// await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) +// await this.deleteAllDatabasesByConnectionTypeApi('SENTINEL') +// } +// } +// +// async deleteAllDatabasesByConnectionTypeApi(connectionType: string): Promise { +// const allDatabases = await this.getAllDatabases() +// const databaseIds = allDatabases.filter(db => db.connectionType === connectionType).map(db => db.id) +// +// if (databaseIds.length) { +// await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) +// } +// } +// +// async getClusterNodesApi(databaseParameters: OSSClusterParameters): Promise { +// const databaseId = await this.getDatabaseIdByName(databaseParameters.ossClusterDatabaseName) +// const resourcePath = `${ResourcePath.Databases}/${databaseId}${ResourcePath.ClusterDetails}` +// const response = await this.httpClient.get<{ nodes: ClusterNodes[] }>(resourcePath) +// +// return response.nodes.map(node => `${node.host}:${node.port}`) +// } +// } + +import { asyncFilter, doAsyncStuff } from '../async-helper' + +const apiUrl = 'https://localhost:5540/api' - constructor(baseURL: string) { - this.httpClient = new HttpClient(baseURL) - } + +const apiClient = axios.create({ + baseURL: apiUrl, // Change to your API base URL + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false // Allows self-signed/invalid SSL certs + }) +}) + +// Enable logging if DEBUG=1 is set +if (process.env.DEBUG === '1') { + apiClient.interceptors.request.use(request => { + console.log('Starting Request', request) + return request + }) + + apiClient.interceptors.response.use(response => { + console.log('Response:', response) + return response + }, error => { + console.error('Error Response:', error.response) + return Promise.reject(error) + }) +} + +export class DatabaseAPIRequests { async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false): Promise { const uniqueId = faker.string.alphanumeric({ length: 10 }) const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) - const requestBody: any = { name: databaseParameters.databaseName, host: databaseParameters.host, port: Number(databaseParameters.port), username: databaseParameters.databaseUsername, - password: databaseParameters.databasePassword, + password: databaseParameters.databasePassword } - if (databaseParameters.caCert && databaseParameters.clientCert) { + if (databaseParameters.caCert) { requestBody.tls = true requestBody.verifyServerCert = false requestBody.caCert = { name: `ca-${uniqueId}`, - certificate: databaseParameters.caCert.certificate, + certificate: databaseParameters.caCert.certificate } requestBody.clientCert = { name: `client-${uniqueId}`, - certificate: databaseParameters.clientCert.certificate, - key: databaseParameters.clientCert.key, + certificate: databaseParameters.clientCert!.certificate, + key: databaseParameters.clientCert!.key } } @@ -112,68 +239,42 @@ export class DatabaseAPIRequests { requestBody.cloudDetails = { cloudId: uniqueIdNumber, subscriptionType: 'fixed', - planMemoryLimit: 100, + planMemoryLimit: 30, memoryLimitMeasurementUnit: 'mb', - free: true, + free: true } } - const response = await this.httpClient.post(ResourcePath.Databases, requestBody) - - if (!response || Object.keys(response).length === 0) { - throw new Error('The response body is empty') - } - - if (response.name !== databaseParameters.databaseName) { - throw new Error(`Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}`) - } + const response = await apiClient.post(ResourcePath.Databases, requestBody) + if (response.status !== 201) throw new Error(`Database creation failed for ${databaseParameters.databaseName}`) } - async getAllDatabases(): Promise { - return this.httpClient.get(ResourcePath.Databases) + async getAllDatabases(): Promise { + const response = await apiClient.get(ResourcePath.Databases) + if (response.status !== 200) throw new Error('Failed to retrieve databases') + return response.data } async getDatabaseIdByName(databaseName?: string): Promise { if (!databaseName) throw new Error('Error: Missing databaseName') const allDatabases = await this.getAllDatabases() - const db = allDatabases.find(db => db.name === databaseName) - if (!db) throw new Error(`Database ${databaseName} not found`) + const filteredDb = await asyncFilter(allDatabases, async (item: databaseParameters) => { + await doAsyncStuff() + return item.name === databaseName + }) - return db.id + if (filteredDb.length === 0) throw new Error(`Database ${databaseName} not found`) + return filteredDb[0].id } async deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise { const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName) if (!databaseId) throw new Error('Error: Missing databaseId') - await this.httpClient.delete(ResourcePath.Databases, { ids: [`${databaseId}`] }) - } - - async deleteAllDatabasesApi(): Promise { - const allDatabases = await this.getAllDatabases() - const databaseIds = allDatabases.map(db => db.id) - - if (databaseIds.length) { - await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) - await this.deleteAllDatabasesByConnectionTypeApi('SENTINEL') - } - } - - async deleteAllDatabasesByConnectionTypeApi(connectionType: string): Promise { - const allDatabases = await this.getAllDatabases() - const databaseIds = allDatabases.filter(db => db.connectionType === connectionType).map(db => db.id) - - if (databaseIds.length) { - await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) - } - } - - async getClusterNodesApi(databaseParameters: OSSClusterParameters): Promise { - const databaseId = await this.getDatabaseIdByName(databaseParameters.ossClusterDatabaseName) - const resourcePath = `${ResourcePath.Databases}/${databaseId}${ResourcePath.ClusterDetails}` - const response = await this.httpClient.get<{ nodes: ClusterNodes[] }>(resourcePath) - - return response.nodes.map(node => `${node.host}:${node.port}`) + const requestBody = { ids: [databaseId] } + const response = await apiClient.delete(ResourcePath.Databases, { data: requestBody }) + if (response.status !== 200) throw new Error(`Failed to delete database ${databaseParameters.databaseName}`) } } + diff --git a/tests/playwright/helpers/async-helper.ts b/tests/playwright/helpers/async-helper.ts new file mode 100644 index 0000000000..062ef23c6d --- /dev/null +++ b/tests/playwright/helpers/async-helper.ts @@ -0,0 +1,28 @@ +/** + * Helper function to work with arr.filter() method with async functions + * @param array The array + * @param callback The callback function need to be processed + */ +async function asyncFilter(array: string[], callback: (item: any) => Promise): Promise { + const fail = Symbol(); + return (await Promise.all(array.map(async item => (await callback(item)) ? item : fail))).filter(i => i !== fail); +} + +/** + * Helper function to work with arr.find() method with async functions + * @param array The array + * @param asyncCallback The callback function need to be processed + */ +async function asyncFind(array: string[], asyncCallback: (item: any) => Promise): Promise { + const index = (await Promise.all(array.map(asyncCallback))).findIndex(result => result); + return array[index]; +} + +/** + * Helper function for waiting until promise be resolved + */ +function doAsyncStuff(): Promise { + return Promise.resolve(); +} + +export { asyncFilter, asyncFind, doAsyncStuff }; \ No newline at end of file From a5003312158148e796b4373965ed2a313291cb0a Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 16:55:40 +0200 Subject: [PATCH 027/128] simplify http client --- tests/playwright/helpers/api/api-databases.ts | 215 +----------------- tests/playwright/helpers/api/http-client.ts | 81 +++---- 2 files changed, 38 insertions(+), 258 deletions(-) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 50e0a59df0..a6744ec088 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -1,215 +1,22 @@ -// -// import { faker } from '@faker-js/faker' -// import { HttpClient } from './http-client' -// import { AddNewDatabaseParameters } from '../../pageObjects/dialogs/add-redis-database-dialog' -// import { ResourcePath } from '../constants' -// -// export class DatabaseAPIRequests { -// private httpClient: HttpClient -// -// constructor(baseURL: string) { -// this.httpClient = new HttpClient(baseURL) -// } -// -// /** -// * Add a new standalone database using the HTTP client. -// * @param databaseParameters The database parameters -// * @param isCloud Whether the database is cloud-based -// */ -// async addNewStandaloneDatabaseApi( -// databaseParameters: AddNewDatabaseParameters, -// isCloud = false -// ): Promise { -// const uniqueId = faker.string.alphanumeric({ length: 10 }) -// const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) -// -// const requestBody: any = { -// name: databaseParameters.databaseName, -// host: databaseParameters.host, -// port: Number(databaseParameters.port), -// username: databaseParameters.databaseUsername, -// password: databaseParameters.databasePassword, -// } -// -// if (databaseParameters.caCert && databaseParameters.clientCert) { -// requestBody.tls = true -// requestBody.verifyServerCert = false -// requestBody.caCert = { -// name: `ca-${uniqueId}`, -// certificate: databaseParameters.caCert.certificate, -// } -// requestBody.clientCert = { -// name: `client-${uniqueId}`, -// certificate: databaseParameters.clientCert.certificate, -// key: databaseParameters.clientCert.key, -// } -// } -// -// if (isCloud) { -// requestBody.cloudDetails = { -// cloudId: uniqueIdNumber, -// subscriptionType: 'fixed', -// planMemoryLimit: 100, -// memoryLimitMeasurementUnit: 'mb', -// free: true, -// } -// } -// -// const response = await this.httpClient.post(ResourcePath.Databases, requestBody) -// -// if (!response || Object.keys(response).length === 0) { -// throw new Error('The response body is empty') -// } -// -// if (response.name !== databaseParameters.databaseName) { -// throw new Error( -// `Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}` -// ) -// } -// -// console.log('Database created successfully:', response) -// } -// } import { faker } from '@faker-js/faker' -import axios from 'axios' -import { Chance } from 'chance' +import {AxiosInstance} from 'axios' + import { HttpClient } from './http-client' -import { AddNewDatabaseParameters, OSSClusterParameters, ClusterNodes, databaseParameters } from '../../types' +import { AddNewDatabaseParameters, databaseParameters } from '../../types' import { ResourcePath } from '../constants' -// export class DatabaseAPIRequests { -// private httpClient: HttpClient -// -// constructor(baseURL: string) { -// this.httpClient = new HttpClient(baseURL) -// } -// -// async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false): Promise { -// const uniqueId = faker.string.alphanumeric({ length: 10 }) -// const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) -// -// const requestBody: any = { -// name: databaseParameters.databaseName, -// host: databaseParameters.host, -// port: Number(databaseParameters.port), -// username: databaseParameters.databaseUsername, -// password: databaseParameters.databasePassword, -// } -// -// if (databaseParameters.caCert && databaseParameters.clientCert) { -// requestBody.tls = true -// requestBody.verifyServerCert = false -// requestBody.caCert = { -// name: `ca-${uniqueId}`, -// certificate: databaseParameters.caCert.certificate, -// } -// requestBody.clientCert = { -// name: `client-${uniqueId}`, -// certificate: databaseParameters.clientCert.certificate, -// key: databaseParameters.clientCert.key, -// } -// } -// -// if (isCloud) { -// requestBody.cloudDetails = { -// cloudId: uniqueIdNumber, -// subscriptionType: 'fixed', -// planMemoryLimit: 100, -// memoryLimitMeasurementUnit: 'mb', -// free: true, -// } -// } -// -// const response = await this.httpClient.post(ResourcePath.Databases, requestBody) -// -// if (!response || Object.keys(response).length === 0) { -// throw new Error('The response body is empty') -// } -// -// if (response.name !== databaseParameters.databaseName) { -// throw new Error(`Database name mismatch. Expected: ${databaseParameters.databaseName}, Received: ${response.name}`) -// } -// } -// -// async getAllDatabases(): Promise { -// return this.httpClient.get(ResourcePath.Databases) -// } -// -// async getDatabaseIdByName(databaseName?: string): Promise { -// if (!databaseName) throw new Error('Error: Missing databaseName') -// -// const allDatabases = await this.getAllDatabases() -// const db = allDatabases.find(db => db.name === databaseName) -// if (!db) throw new Error(`Database ${databaseName} not found`) -// -// return db.id -// } -// -// async deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise { -// const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName) -// if (!databaseId) throw new Error('Error: Missing databaseId') -// -// await this.httpClient.delete(ResourcePath.Databases, { ids: [`${databaseId}`] }) -// } -// -// async deleteAllDatabasesApi(): Promise { -// const allDatabases = await this.getAllDatabases() -// const databaseIds = allDatabases.map(db => db.id) -// -// if (databaseIds.length) { -// await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) -// await this.deleteAllDatabasesByConnectionTypeApi('SENTINEL') -// } -// } -// -// async deleteAllDatabasesByConnectionTypeApi(connectionType: string): Promise { -// const allDatabases = await this.getAllDatabases() -// const databaseIds = allDatabases.filter(db => db.connectionType === connectionType).map(db => db.id) -// -// if (databaseIds.length) { -// await this.httpClient.delete(ResourcePath.Databases, { ids: databaseIds }) -// } -// } -// -// async getClusterNodesApi(databaseParameters: OSSClusterParameters): Promise { -// const databaseId = await this.getDatabaseIdByName(databaseParameters.ossClusterDatabaseName) -// const resourcePath = `${ResourcePath.Databases}/${databaseId}${ResourcePath.ClusterDetails}` -// const response = await this.httpClient.get<{ nodes: ClusterNodes[] }>(resourcePath) -// -// return response.nodes.map(node => `${node.host}:${node.port}`) -// } -// } - import { asyncFilter, doAsyncStuff } from '../async-helper' -const apiUrl = 'https://localhost:5540/api' - +export class DatabaseAPIRequests { -const apiClient = axios.create({ - baseURL: apiUrl, // Change to your API base URL - httpsAgent: new (require('https').Agent)({ - rejectUnauthorized: false // Allows self-signed/invalid SSL certs - }) -}) + private apiClient: AxiosInstance -// Enable logging if DEBUG=1 is set -if (process.env.DEBUG === '1') { - apiClient.interceptors.request.use(request => { - console.log('Starting Request', request) - return request - }) + constructor(apiUrl: string) { + this.apiClient = new HttpClient(apiUrl).getClient() - apiClient.interceptors.response.use(response => { - console.log('Response:', response) - return response - }, error => { - console.error('Error Response:', error.response) - return Promise.reject(error) - }) -} + } -export class DatabaseAPIRequests { async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false): Promise { const uniqueId = faker.string.alphanumeric({ length: 10 }) const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) @@ -245,12 +52,12 @@ export class DatabaseAPIRequests { } } - const response = await apiClient.post(ResourcePath.Databases, requestBody) + const response = await this.apiClient.post(ResourcePath.Databases, requestBody) if (response.status !== 201) throw new Error(`Database creation failed for ${databaseParameters.databaseName}`) } async getAllDatabases(): Promise { - const response = await apiClient.get(ResourcePath.Databases) + const response = await this.apiClient.get(ResourcePath.Databases) if (response.status !== 200) throw new Error('Failed to retrieve databases') return response.data } @@ -273,7 +80,7 @@ export class DatabaseAPIRequests { if (!databaseId) throw new Error('Error: Missing databaseId') const requestBody = { ids: [databaseId] } - const response = await apiClient.delete(ResourcePath.Databases, { data: requestBody }) + const response = await this.apiClient.delete(ResourcePath.Databases, { data: requestBody }) if (response.status !== 200) throw new Error(`Failed to delete database ${databaseParameters.databaseName}`) } } diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index a54e6f5106..57db62b894 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -1,66 +1,39 @@ -import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios' -import https from 'https' - -// Logging interceptor for requests -const requestLogger = (config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => { - console.log('Request:', config.method?.toUpperCase(), config.url) - console.log('Headers:', config.headers) - console.log('Body:', config.data) - return config -} - -// Logging interceptor for responses -const responseLogger = (response: AxiosResponse): AxiosResponse => { - console.log('Response:', response.status, response.data) - return response -} +import axios, { AxiosInstance } from 'axios' export class HttpClient { - private instance: AxiosInstance - constructor(baseURL: string) { - // if (!baseURL) { - // throw new Error('No URL must be passed in') - // } - this.instance = axios.create({ - baseURL, - httpsAgent: new https.Agent({ - rejectUnauthorized: false // allow self-signed or invalid certificates + private apiUrl: string + private apiClient: AxiosInstance + private static instance: HttpClient + + constructor(apiUrl: string) { + this.apiUrl = apiUrl + this.apiClient = axios.create({ + baseURL: this.apiUrl, // Change to your API base URL + httpsAgent: new (require('https').Agent)({ + rejectUnauthorized: false // Allows self-signed/invalid SSL certs }) }) - // Setup interceptors for logging - this.instance.interceptors.request.use( - requestLogger, - error => Promise.reject(error) - ) - this.instance.interceptors.response.use( - responseLogger, - error => Promise.reject(error) - ) - } - - async get(url: string, headers?: Record): Promise { - const response = await this.instance.get(url, { headers }) - if (!response.data) throw new Error('GET request returned empty response') - return response.data + // Enable logging if DEBUG=1 is set + if (process.env.DEBUG === '1') { + this.apiClient.interceptors.request.use(request => { + console.log('Starting Request', request) + return request + }) + this.apiClient.interceptors.response.use(response => { + console.log('Response:', response) + return response + }, error => { + console.error('Error Response:', error.response) + return Promise.reject(error) + }) + } } - async post(url: string, body: unknown, headers?: Record): Promise { - const response = await this.instance.post(url, body, { headers }) - if (!response.data) throw new Error('Empty response body on POST request') - return response.data + getClient(): AxiosInstance { + return this.apiClient } - async put(url: string, body: unknown, headers?: Record): Promise { - const response = await this.instance.put(url, body, { headers }) - if (!response.data) throw new Error('Empty response body on PUT request') - return response.data - } - async delete(url: string, headers?: Record): Promise { - const response = await this.instance.delete(url, { headers }) - if (!response.data) throw new Error('Empty response body from DELETE request') - return response.data - } } From a92c5a7de364ad820c46ac5f0622b631e925fb25 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 17:04:42 +0200 Subject: [PATCH 028/128] Web POC done --- tests/playwright/helpers/api/api-databases.ts | 4 - tests/playwright/helpers/api/api-keys.ts | 184 ++---------------- 2 files changed, 20 insertions(+), 168 deletions(-) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index a6744ec088..ec0e159f1b 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -1,20 +1,16 @@ import { faker } from '@faker-js/faker' import {AxiosInstance} from 'axios' - import { HttpClient } from './http-client' import { AddNewDatabaseParameters, databaseParameters } from '../../types' import { ResourcePath } from '../constants' - import { asyncFilter, doAsyncStuff } from '../async-helper' - export class DatabaseAPIRequests { private apiClient: AxiosInstance constructor(apiUrl: string) { this.apiClient = new HttpClient(apiUrl).getClient() - } async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false): Promise { diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index fb4a5be276..0ca4347dad 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -1,174 +1,30 @@ -import axios from 'axios' -// import { HttpClient } from './http-client' +import {AxiosInstance} from 'axios' +import { HttpClient } from './http-client' import { DatabaseAPIRequests } from './api-databases' import { AddNewDatabaseParameters, HashKeyParameters, - StringKeyParameters, - ListKeyParameters, SetKeyParameters, - SortedSetKeyParameters, StreamKeyParameters} from '../../types' -// const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' -// -// export class APIKeyRequests { -// private httpClient: HttpClient -// -// private databaseAPIRequests : DatabaseAPIRequests -// -// constructor(baseURL: string) { -// this.httpClient = new HttpClient(baseURL) -// this.databaseAPIRequests = new DatabaseAPIRequests(baseURL) -// } -// -// private async getDatabaseId(databaseName: string): Promise { -// const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) -// if (!databaseId) throw new Error(`Database with name ${databaseName} not found`) -// return databaseId -// } -// -// // async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// // const requestBody = { -// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// // fields: keyParameters.fields.map(fields => ({ -// // ...fields, -// // field: Buffer.from(fields.field, 'utf-8'), -// // value: Buffer.from(fields.value, 'utf-8') -// // })) -// // } -// // -// // await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) -// // } -// // -// // async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// // const requestBody = { -// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// // entries: keyParameters.entries.map(member => ({ -// // ...member, -// // fields: member.fields.map(({ name, value }) => ({ -// // name: Buffer.from(name, 'utf-8'), -// // value: Buffer.from(value, 'utf-8') -// // })) -// // })) -// // } -// // -// // await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) -// // } -// // -// // async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// // const requestBody = { -// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// // members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) -// // } -// // -// // await this.httpClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) -// // } -// // -// // async addSortedSetKeyApi(keyParameters: SortedSetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// // const requestBody = { -// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// // members: keyParameters.members.map(member => ({ ...member, name: Buffer.from(member.name, 'utf-8') })) -// // } -// // -// // await this.httpClient.post(`/databases/${databaseId}/zSet?encoding=buffer`, requestBody) -// // } -// // -// // async addListKeyApi(keyParameters: ListKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// // const requestBody = { -// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// // element: Buffer.from(keyParameters.element, 'utf-8') -// // } -// // -// // await this.httpClient.post(`/databases/${databaseId}/list?encoding=buffer`, requestBody) -// // } -// // -// // async addStringKeyApi(keyParameters: StringKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// // const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// // const requestBody = { -// // keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// // value: Buffer.from(keyParameters.value, 'utf-8') -// // } -// // -// // await this.httpClient.post(`/databases/${databaseId}/string?encoding=buffer`, requestBody) -// // } -// -// -// -// async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// const requestBody = { -// keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// fields: keyParameters.fields.map(fields => ({ -// ...fields, -// field: Buffer.from(fields.field, 'utf-8'), -// value: Buffer.from(fields.value, 'utf-8') -// })) -// } -// -// await this.httpClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) -// } -// -// async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { -// const databaseId = await this.getDatabaseId(databaseParameters.databaseName) -// const requestBody = { -// keyName: Buffer.from(keyParameters.keyName, 'utf-8'), -// entries: keyParameters.entries.map(member => ({ -// ...member, -// fields: member.fields.map(({ name, value }) => ({ -// name: Buffer.from(name, 'utf-8'), -// value: Buffer.from(value, 'utf-8') -// })) -// })) -// } -// -// await this.httpClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) -// } -// -// async deleteKeyByNameApi(keyName: string, databaseName: string): Promise { -// const databaseId = await this.getDatabaseId(databaseName) -// // const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } -// const requestBody = { keyNames: keyName } -// await this.httpClient.delete(`/databases/${databaseId}/keys?encoding=buffer`, requestBody) -// } -// } -const apiUrl = 'https://localhost:5540/api' -const databaseAPIRequests = new DatabaseAPIRequests(apiUrl) -const apiClient = axios.create({ - baseURL: apiUrl, - httpsAgent: new (require('https').Agent)({ - rejectUnauthorized: false // Allows self-signed/invalid SSL certs - }) -}) - -// Logging request and response -apiClient.interceptors.request.use(request => { - console.log('Starting Request', request) - return request -}) - -apiClient.interceptors.response.use(response => { - console.log('Response:', response) - return response -}, error => { - console.error('Error Response:', error.response) - return Promise.reject(error) -}) const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' export class APIKeyRequests { + private databaseAPIRequests: DatabaseAPIRequests + private apiClient: AxiosInstance + + constructor(apiUrl: string) { + this.apiClient = new HttpClient(apiUrl).getClient() + this.databaseAPIRequests = new DatabaseAPIRequests(apiUrl) + } + async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), fields: keyParameters.fields.map(fields => ({ @@ -177,12 +33,12 @@ export class APIKeyRequests { value: Buffer.from(fields.value, 'utf-8') })) } - const response = await apiClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) + const response = await this.apiClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) if (response.status !== 201) throw new Error('The creation of new Hash key request failed') } async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), entries: keyParameters.entries.map(member => ({ @@ -193,17 +49,17 @@ export class APIKeyRequests { })) })) } - const response = await apiClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) + const response = await this.apiClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) if (response.status !== 201) throw new Error('The creation of new Stream key request failed') } async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) } - const response = await apiClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) + const response = await this.apiClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) if (response.status !== 201) throw new Error('The creation of new Set key request failed') } @@ -212,18 +68,18 @@ export class APIKeyRequests { cursor: '0', match: keyName } - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) - const response = await apiClient.post(bufferPathMask.replace('databaseId', databaseId), requestBody) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) + const response = await this.apiClient.post(bufferPathMask.replace('databaseId', databaseId), requestBody) if (response.status !== 200) throw new Error('Getting key request failed') return response.data[0].keys } async deleteKeyByNameApi(keyName: string, databaseName: string): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) const doesKeyExist = await this.searchKeyByNameApi(keyName, databaseName) if (doesKeyExist.length > 0) { const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } - const response = await apiClient.delete(bufferPathMask.replace('databaseId', databaseId), { data: requestBody }) + const response = await this.apiClient.delete(bufferPathMask.replace('databaseId', databaseId), { data: requestBody }) if (response.status !== 200) throw new Error('The deletion of the key request failed') } } From 1b587770d526caa3e74c749a5c311fae3add8944 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 17:30:08 +0200 Subject: [PATCH 029/128] project set up for electron tests --- tests/playwright/package.json | 1 + tests/playwright/playwright.config.ts | 2 + tests/playwright/tests/addKeys.spec.ts | 39 ------------------ ...ample.spec.ts => example.electron.spec.ts} | 0 tests/playwright/tests/example.web.spec.ts | 40 +++++++++++++++++++ 5 files changed, 43 insertions(+), 39 deletions(-) delete mode 100644 tests/playwright/tests/addKeys.spec.ts rename tests/playwright/tests/{example.spec.ts => example.electron.spec.ts} (100%) create mode 100644 tests/playwright/tests/example.web.spec.ts diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 5fbb953811..22ecf04681 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -16,6 +16,7 @@ "allTests": "playwright test", "generateReports": "allure generate --clean", "test:chromium": "playwright test --project=localChromium", + "test:electron": "playwright test --project=localElectron", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 3c29ddfb58..f8844826d4 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -91,6 +91,7 @@ export default defineConfig({ projects: [ { name: 'localChromium', + testMatch: ['**.web.spec.ts'], use: { ...devices['Desktop Chrome'], baseURL: process.env.COMMON_URL || 'https://localhost:5540', @@ -109,6 +110,7 @@ export default defineConfig({ }, { name: 'localElectron', + testMatch: ['**.electron.spec.ts'], use: { ...devices['Desktop Chrome'], baseURL: 'https://chrome.desktop/', diff --git a/tests/playwright/tests/addKeys.spec.ts b/tests/playwright/tests/addKeys.spec.ts deleted file mode 100644 index ac5dbeee6b..0000000000 --- a/tests/playwright/tests/addKeys.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -// import { rte } from '../helpers/constants' -// import { Common } from '../helpers/common' -// // import { DatabaseHelper } from '../../../../helpers/database'; -// // import { BrowserPage } from '../../../../pageObjects'; -// // import { commonUrl, ossStandaloneConfig } from '../../../../helpers/conf'; -// -// // import { DatabaseAPIRequests } from '../../../../helpers/api/api-database'; -// // import { APIKeyRequests } from '../../../../helpers/api/api-keys'; -// // -// // const browserPage = new BrowserPage(); -// // const databaseHelper = new DatabaseHelper(); -// // const databaseAPIRequests = new DatabaseAPIRequests(); -// // const apiKeyRequests = new APIKeyRequests(); -// // -// let keyName = Common.generateWord(10) //done -// -// fixture `Add keys` //NA -// .meta({ type: 'smoke', rte: rte.standalone }) -// .page(commonUrl) -// .beforeEach(async() => { -// await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(ossStandaloneConfig); -// }) -// .afterEach(async() => { -// // Clear and delete database -// await apiKeyRequests.deleteKeyByNameApi(keyName, ossStandaloneConfig.databaseName); -// await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); -// }); -// test('Verify that user can add Hash Key', async t => { -// keyName = Common.generateWord(10); -// // Add Hash key -// await browserPage.addHashKey(keyName); -// // Check the notification message -// const notification = browserPage.Toast.toastHeader.textContent; -// await t.expect(notification).contains('Key has been added', 'The notification not displayed'); -// // Check that new key is displayed in the list -// await browserPage.searchByKeyName(keyName); -// const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); -// await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); -// }); diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.electron.spec.ts similarity index 100% rename from tests/playwright/tests/example.spec.ts rename to tests/playwright/tests/example.electron.spec.ts diff --git a/tests/playwright/tests/example.web.spec.ts b/tests/playwright/tests/example.web.spec.ts new file mode 100644 index 0000000000..1291239b61 --- /dev/null +++ b/tests/playwright/tests/example.web.spec.ts @@ -0,0 +1,40 @@ +import {test, expect} from '../fixtures/open-ri' +import {Common} from '../helpers/common' +import {BrowserPage} from '../pageObjects/browser-page' +import {APIKeyRequests} from "../helpers/api/api-keys"; + +let keyName: string +let browserPage: BrowserPage + +test.beforeEach(async ({basePage}) => { + + console.log('WE ARE IN THE BEFORE STEP') + keyName = Common.generateAlpanumeric(10) + browserPage = new BrowserPage(basePage) + +}) + +test.afterEach(async ({basePage, workerState }) => { + console.log('WE ARE IN THE AFTER STEP') + const apiKeyClient = new APIKeyRequests(workerState.apiUrl) + await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) + +}) + +test('basic test', async ({ basePage}) => { + + console.log('WE ARE IN TEST') + await browserPage.addHashKey(keyName) + + // checks that the notification is displayed (should be in a different test) + await expect(await basePage.getByText('Key has been added')).toBeVisible() + + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + + + +}) From 79f17b187f81c738f2a18fb9f9b58f744236d4fb Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 17:40:43 +0200 Subject: [PATCH 030/128] small changes in formating and wait plus comments --- .../destroy_local_environment.sh | 2 +- .../playwright/tests/example.electron.spec.ts | 28 +++++++++---------- tests/playwright/tests/example.web.spec.ts | 7 ++--- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/tests/playwright/local-docker-environment/destroy_local_environment.sh b/tests/playwright/local-docker-environment/destroy_local_environment.sh index 7224d6c83e..203a22e443 100755 --- a/tests/playwright/local-docker-environment/destroy_local_environment.sh +++ b/tests/playwright/local-docker-environment/destroy_local_environment.sh @@ -1,5 +1,5 @@ #!/bin/bash docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml down && -sleep 1 +sleep 3 rm -rf plugins remote report results rte test-data rihomedir diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 1291239b61..8e356d5158 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -9,31 +9,31 @@ let browserPage: BrowserPage test.beforeEach(async ({basePage}) => { console.log('WE ARE IN THE BEFORE STEP') - keyName = Common.generateAlpanumeric(10) - browserPage = new BrowserPage(basePage) + // keyName = Common.generateAlpanumeric(10) + // browserPage = new BrowserPage(basePage) }) test.afterEach(async ({basePage, workerState }) => { console.log('WE ARE IN THE AFTER STEP') - const apiKeyClient = new APIKeyRequests(workerState.apiUrl) - await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) + // const apiKeyClient = new APIKeyRequests(workerState.apiUrl) + // await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) }) test('basic test', async ({ basePage}) => { console.log('WE ARE IN TEST') - await browserPage.addHashKey(keyName) - - // checks that the notification is displayed (should be in a different test) - await expect(await basePage.getByText('Key has been added')).toBeVisible() - - - // Check that new key is displayed in the list - await browserPage.searchByKeyName(keyName) - const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) - await expect(isKeyIsDisplayedInTheList).toBe(true) + // await browserPage.addHashKey(keyName) + // + // // checks that the notification is displayed (should be in a different test) + // await expect(await basePage.getByText('Key has been added')).toBeVisible() + // + // + // // Check that new key is displayed in the list + // await browserPage.searchByKeyName(keyName) + // const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) + // await expect(isKeyIsDisplayedInTheList).toBe(true) diff --git a/tests/playwright/tests/example.web.spec.ts b/tests/playwright/tests/example.web.spec.ts index 1291239b61..b43c025e51 100644 --- a/tests/playwright/tests/example.web.spec.ts +++ b/tests/playwright/tests/example.web.spec.ts @@ -1,7 +1,7 @@ import {test, expect} from '../fixtures/open-ri' import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' -import {APIKeyRequests} from "../helpers/api/api-keys"; +import {APIKeyRequests} from '../helpers/api/api-keys' let keyName: string let browserPage: BrowserPage @@ -21,7 +21,7 @@ test.afterEach(async ({basePage, workerState }) => { }) -test('basic test', async ({ basePage}) => { +test('basic test', async ({basePage}) => { console.log('WE ARE IN TEST') await browserPage.addHashKey(keyName) @@ -29,12 +29,9 @@ test('basic test', async ({ basePage}) => { // checks that the notification is displayed (should be in a different test) await expect(await basePage.getByText('Key has been added')).toBeVisible() - // Check that new key is displayed in the list await browserPage.searchByKeyName(keyName) const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) await expect(isKeyIsDisplayedInTheList).toBe(true) - - }) From de6cac1ca4031351b2f50507bf7a1cf9df168438 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Sun, 16 Mar 2025 23:41:30 +0200 Subject: [PATCH 031/128] app starting from unpacked app image --- tests/playwright/fixtures/electron.ts | 52 +++++++++++++++++++ tests/playwright/playwright.config.ts | 3 +- .../playwright/tests/example.electron.spec.ts | 12 ++--- 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 tests/playwright/fixtures/electron.ts diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts new file mode 100644 index 0000000000..882ab15dd2 --- /dev/null +++ b/tests/playwright/fixtures/electron.ts @@ -0,0 +1,52 @@ +import { test as base, ElectronApplication, Page} from '@playwright/test' +import { _electron as electron } from 'playwright' +import {DatabaseAPIRequests} from "../helpers/api/api-databases"; + +type ElectronFixtures = { + electronApp: ElectronApplication; + mainWindow: Page; +} +// /home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight +// /home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/resources/app.asar/dist/renderer/index.html' +export const test = base.extend({ + + electronApp: async ({}, use) => { + // Launch Electron App + const electronApp = await electron.launch({ + executablePath: '/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight', + args: ['index.html'], // Adjust the path to your Electron main entry file + }) + + // Evaluation expression in the Electron context. + const appPath = await electronApp.evaluate(async ({ app }) => { + // This runs in the main Electron process, parameter here is always + // the result of the require('electron') in the main app script. + return app.getAppPath(); + }); + console.log(appPath); + + // Get the first window that the app opens, wait if necessary. + const window = await electronApp.firstWindow(); + // Print the title. + console.log(await window.title()); + // Capture a screenshot. + await window.screenshot({ path: 'intro.png' }); + // Direct Electron console to Node terminal. + window.on('console', console.log); + + + try { + await use(electronApp) + } finally { + await electronApp.close() + } + }, + + mainWindow: async ({ electronApp }, use) => { + // Get the first window of the Electron app + const window = await electronApp.firstWindow() + await use(window) + }, +}) + +export { expect } from '@playwright/test' diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index f8844826d4..8fc8fbf075 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -112,9 +112,8 @@ export default defineConfig({ name: 'localElectron', testMatch: ['**.electron.spec.ts'], use: { - ...devices['Desktop Chrome'], baseURL: 'https://chrome.desktop/', - + headless: false }, }, diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 8e356d5158..89c01df209 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,12 +1,12 @@ -import {test, expect} from '../fixtures/open-ri' -import {Common} from '../helpers/common' +import {test, expect} from '../fixtures/electron' +// import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' -import {APIKeyRequests} from "../helpers/api/api-keys"; +// import {APIKeyRequests} from "../helpers/api/api-keys"; let keyName: string let browserPage: BrowserPage -test.beforeEach(async ({basePage}) => { +test.beforeEach(async ({electronApp}) => { console.log('WE ARE IN THE BEFORE STEP') // keyName = Common.generateAlpanumeric(10) @@ -14,14 +14,14 @@ test.beforeEach(async ({basePage}) => { }) -test.afterEach(async ({basePage, workerState }) => { +test.afterEach(async ({electronApp}) => { console.log('WE ARE IN THE AFTER STEP') // const apiKeyClient = new APIKeyRequests(workerState.apiUrl) // await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) }) -test('basic test', async ({ basePage}) => { +test('basic test', async ({}) => { console.log('WE ARE IN TEST') // await browserPage.addHashKey(keyName) From 1bb66ef90a1ff138ef090bfb6ca5f27891753f0b Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 10:22:24 +0200 Subject: [PATCH 032/128] more setup for electron --- tests/playwright/fixtures/electron.ts | 75 +++++++++++--- tests/playwright/fixtures/open-ri.ts | 97 ++----------------- .../playwright/local-docker-environment/.env | 89 ++++++++++++++++- tests/playwright/package.json | 3 +- tests/playwright/playwright.config.ts | 3 +- tests/playwright/yarn.lock | 28 ++++++ 6 files changed, 186 insertions(+), 109 deletions(-) diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts index 882ab15dd2..e92acad78e 100644 --- a/tests/playwright/fixtures/electron.ts +++ b/tests/playwright/fixtures/electron.ts @@ -1,38 +1,83 @@ import { test as base, ElectronApplication, Page} from '@playwright/test' -import { _electron as electron } from 'playwright' +import { _electron as electron} from 'playwright' +import {ossStandaloneConfig} from "../helpers/conf"; import {DatabaseAPIRequests} from "../helpers/api/api-databases"; -type ElectronFixtures = { +type WorkerSharedState = { + apiUrl: string; + dbConfig: typeof ossStandaloneConfig; + baseUrl: string; +} + +type ElectronFixture = { electronApp: ElectronApplication; mainWindow: Page; } +// /home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage // /home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight // /home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/resources/app.asar/dist/renderer/index.html' -export const test = base.extend({ +export const test = base.extend({ + workerState: [async ({}, use, testInfo) => { + console.log(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`) + + // Initialize worker-scoped data + const workerState: WorkerSharedState = { + apiUrl: testInfo.project.use.apiUrl, + dbConfig: ossStandaloneConfig, + baseUrl: testInfo.project.use.baseURL + } + console.log(`🏠 Base URL: ${workerState.baseUrl}`) + console.log(`🌐 API URL: ${workerState.apiUrl}`) + console.log(`🗄️ Database Config: ${JSON.stringify(workerState.dbConfig)}`) + + await use(workerState) + + }, { scope: 'worker' }], + forEachWorker: [async ({ workerState }, use) => { + const ti = base.info().workerIndex + console.log(`BEFORE Starting test worker ${ti}`) + + // Set up the database before tests + const dbApi = new DatabaseAPIRequests(workerState.apiUrl) + await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) + + await use() // Run the tests + // Something failing here doesn't affect test execution result + console.log(`Stopping test worker ${ti}`) - electronApp: async ({}, use) => { + // Cleanup after all tests in this worker + + // throw new Error("test worker error") + await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + + }, { scope: 'worker', auto: true }], + electronApp: async ({baseURL}, use) => { // Launch Electron App const electronApp = await electron.launch({ - executablePath: '/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight', - args: ['index.html'], // Adjust the path to your Electron main entry file + executablePath: baseURL, + args: ['index.html'] // Adjust the path to your Electron main entry file + }) // Evaluation expression in the Electron context. - const appPath = await electronApp.evaluate(async ({ app }) => { + const appPath = await electronApp.evaluate(async ({ app }) => // This runs in the main Electron process, parameter here is always // the result of the require('electron') in the main app script. - return app.getAppPath(); - }); - console.log(appPath); + app.getAppPath() + ) + console.log(appPath) // Get the first window that the app opens, wait if necessary. - const window = await electronApp.firstWindow(); + const window = await electronApp.firstWindow() + // Print the title. - console.log(await window.title()); + console.log(await window.title()) // Capture a screenshot. - await window.screenshot({ path: 'intro.png' }); + await window.screenshot({ path: 'intro.png' }) // Direct Electron console to Node terminal. - window.on('console', console.log); + window.on('console', console.log) + try { @@ -45,6 +90,8 @@ export const test = base.extend({ mainWindow: async ({ electronApp }, use) => { // Get the first window of the Electron app const window = await electronApp.firstWindow() + console.log('IN MAIN WINDOW') + await use(window) }, }) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 43d86ec98a..107d243504 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -1,91 +1,10 @@ -// import { test as base } from '@playwright/test' -// import BasePage from '../pageObjects/base-page' -// import {UserAgreementDialog} from '../pageObjects/user-agreement-dialog' -// import {DatabaseAPIRequests} from'../helpers/api/api-databases' -// import { ossStandaloneConfig} from '../helpers/conf' -// import {MyRedisDatabasePage} from '../pageObjects/my-redis-databases-page' -// import { APIKeyRequests } from '../helpers/api/api-keys' -// -// type OpenRedisInsight = { -// basePage: BasePage -// dialogUserAgreement: UserAgreementDialog -// // dbAPI: DatabaseAPIRequests -// apiUrl: string -// dbConfig: typeof ossStandaloneConfig -// forEachWorker: void -// } -// -// export const test = base.extend< OpenRedisInsight, -// { -// forEachWorker: void, -// apiUrl:string -// dbConfig: typeof ossStandaloneConfig}> -// ({ -// dbConfig: async ({}, use) => { -// console.log('Fixture setup: Assigning database config') -// await use(ossStandaloneConfig) // Use the imported object directly -// }, -// apiUrl: ['default', { option: true }], -// // dbAPI: async () => { -// // const dbApi = new DatabaseAPIRequests(this.apiUrl) -// // -// // await dbApi.addNewStandaloneDatabaseApi(ossStandaloneConfig) -// // }, -// // context: async ({ browser }, use) => { -// // const context = await browser.newContext() -// // await context.clearCookies() -// // await context.clearPermissions() -// // // await context.storageState({ path: 'emptyState.json' }) -// // await use(context) -// // await context.close() -// // }, -// // basePage: async ({ context }, use) => { -// basePage: async ({ page , dbConfig }, use) => { -// -// -// // const page = await context.newPage() -// // Navigate to page -// const basePage = new BasePage(page) -// await basePage.navigateToHomeUrl() -// -// const myDbPage = new MyRedisDatabasePage(page) -// await myDbPage.clickOnDBByName(dbConfig.databaseName) -// -// await use(basePage) -// -// }, -// dialogUserAgreement: async ({ page }, use) => { -// const userAgreementDialog = new UserAgreementDialog(page) -// // await userAgreementDialog.acceptLicenseTerms() -// await use(new UserAgreementDialog(page)) -// }, -// forEachWorker: [async ({ apiUrl, dbConfig }, use) => { -// // This code runs before all the tests in the worker process. -// const ti = test.info().workerIndex -// console.log(`BEFORE Starting test worker ${ti}`) -// // Set up the fixture. -// // Add new database -// const dbApi = new DatabaseAPIRequests(apiUrl) -// await dbApi.addNewStandaloneDatabaseApi(dbConfig) -// await use() -// // This code runs after all the tests in the worker process. -// console.log(`Stopping test worker ${ti}`) -// const apiKeyClient = new APIKeyRequests(apiUrl) -// // apiKeyClient.deleteKeyByNameApi() -// await dbApi.deleteStandaloneDatabaseApi(dbConfig) -// }, { auto: true }], // automatically starts for every worker. -// }) -// -// export { expect } from '@playwright/test' - import { test as base, Page } from '@playwright/test' import BasePage from '../pageObjects/base-page' import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialog' import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { ossStandaloneConfig } from '../helpers/conf' import { MyRedisDatabasePage } from '../pageObjects/my-redis-databases-page' -import {APIKeyRequests} from "../helpers/api/api-keys"; -// import { APIKeyRequests } from '../helpers/api/api-keys' + // Define shared worker object type WorkerSharedState = { @@ -95,7 +14,7 @@ type WorkerSharedState = { } // Define test fixture types -type OpenRedisInsight = { +type RedisInsight = { basePage: Page; dialogUserAgreement: UserAgreementDialog; workerState: WorkerSharedState; // Worker-scoped object passed to tests @@ -103,9 +22,9 @@ type OpenRedisInsight = { // Extend Playwright test export const test = base.extend< - OpenRedisInsight, - { forEachWorker: void; workerState: WorkerSharedState } // Worker-scoped fixtures ->({ + RedisInsight, + { forEachWorker: void; workerState: WorkerSharedState } >({ + // ✅ Worker-scoped shared object workerState: [async ({}, use, testInfo) => { console.log(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`) @@ -116,13 +35,13 @@ export const test = base.extend< dbConfig: ossStandaloneConfig, baseUrl: testInfo.project.use.baseURL } - + console.log(`🏠 Base URL: ${workerState.baseUrl}`) console.log(`🌐 API URL: ${workerState.apiUrl}`) console.log(`🗄️ Database Config: ${JSON.stringify(workerState.dbConfig)}`) - console.log(`🏠 Base URL: ${workerState.baseUrl}`) await use(workerState) - }, { scope: 'worker' }], // Runs once per worker + + }, { scope: 'worker' }], // Worker-scoped fixtures, runs once per worker // ✅ Worker-scoped setup/teardown forEachWorker: [async ({ workerState }, use) => { diff --git a/tests/playwright/local-docker-environment/.env b/tests/playwright/local-docker-environment/.env index 822fcf3dac..3a47cf8321 100644 --- a/tests/playwright/local-docker-environment/.env +++ b/tests/playwright/local-docker-environment/.env @@ -6,7 +6,88 @@ RI_NOTIFICATION_UPDATE_URL=https://s3.amazonaws.com/redisinsight.test/public/tes RI_NOTIFICATION_SYNC_INTERVAL=30000 RI_FEATURES_CONFIG_URL=http://static-server:5551/remote/features-config.json RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 -TEST_BIG_DB_DUMP= -RI_ENCRYPTION_KEY= -RI_SERVER_TLS_CERT= -RI_SERVER_TLS_KEY= +TEST_BIG_DB_DUMP=https://s3.amazonaws.com/redisinsight.test/public/rte/dump/big/dump.tar.gz +RI_ENCRYPTION_KEY=ba843af5c43bb6c90538534daf2dd0c2 +RI_SERVER_TLS_CERT='-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIUNPjaFDbh/Y0nCQzp5KYuvYc43zEwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMTAxNzIzNDVaFw0zNDAx +MDcxNzIzNDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCybIVgtjaaRgvJi/mZ91Qi42mXE0ueASXNn5qbMJ85 +dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kWE3lwCn5A1RPOBfDA8voSMPw4HSa9 +Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5fbJXM5uqf1fe1NE+HsUBKOdPBFNl4 +jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rFh3ggXZpDzq79ykLCK6MkaQcXYwls +qghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299v2vyRNm59u51c4g5dKxKPBPQw+06 +bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJyj8GP9Ej7XwQ9XQDiZc7/qzTamvE +yz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfIpHi/AJ//1yfPBqFLWY5B5f5fWQHv +e/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGDr9e25h3N3d6YRYKAMLiRxE2sFfni +79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQhA4gqNYIQ6KHvGDZCn6aNi8EgGYbp +PTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxMAUBesPIRj7kz5B6x3DfsBq/ZOzJW +Um35FPOdqGosqws31WatOTQIoXDV9lf86ew8e3W6O4O42szmcX2bVekNmGQTV9fX +OwIDAQABo1MwUTAdBgNVHQ4EFgQUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwHwYDVR0j +BBgwFoAUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAgEAk/YnVSsxvsNw43acfXHVNMffL3Uwy2v7UMdc0+nmuFtG +dj4RNVyW42jZXaeEzckEBr/HQLY0X4XA3SDRF6OXgdgyAFKSMChU0b7ldXTZicYO +cjOHztNBQ8NHXCSHJjzjJixgIKj88qAHzFNg89lA9RYjhiGhmR6iKe+FGpf8XyG9 +gF1mnqyWFGevsEgmNED/KgM1EZtix84hhuUvo1FoyjM4gkR6IQHkVwfDssRgZUaI +1TFURNLB3YSbjJ+vygBjYZbW29xLQ8MIZnT9uDmUoM1RKg33Fb3clsuF4U6hMOLo +TPFcBwrejY/3eMOZd1JqFN+JS4cRWhHVAk+Eurora76hBowMsYEsOBcrqDTZsFEU +x8qZbQLTCKy4HpvBAAH/P3gOeLEVYwCkTAA1w6/BLodu8dCJnKxylEuHoLSSm3xQ +pxRhb2dZHdd1gUikZ3rbFnUj9YDWz5eWn19FHkYlLXoxrQEkz20Mim7gJvbzLJbG +nPeVnoaFxr//sGJu+Ilq+heXeXqXHorOtfYbSlWJXUix67n+JvtVTHsdQ1PPMgGl +5hAD/oVNgdmWth/k6RDsKkrklkK5rEVkyeescpyhLRJOa/4l2xAQziBfQGijnTTb +NdFUmSSGFeptHPvjNLULF8kfAGEdmrGIttExvCwOci9F/k9OdmlTK2Z8UlGHwIA= +-----END CERTIFICATE-----' +RI_SERVER_TLS_KEY='-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCybIVgtjaaRgvJ +i/mZ91Qi42mXE0ueASXNn5qbMJ85dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kW +E3lwCn5A1RPOBfDA8voSMPw4HSa9Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5f +bJXM5uqf1fe1NE+HsUBKOdPBFNl4jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rF +h3ggXZpDzq79ykLCK6MkaQcXYwlsqghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299 +v2vyRNm59u51c4g5dKxKPBPQw+06bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJ +yj8GP9Ej7XwQ9XQDiZc7/qzTamvEyz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfI +pHi/AJ//1yfPBqFLWY5B5f5fWQHve/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGD +r9e25h3N3d6YRYKAMLiRxE2sFfni79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQh +A4gqNYIQ6KHvGDZCn6aNi8EgGYbpPTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxM +AUBesPIRj7kz5B6x3DfsBq/ZOzJWUm35FPOdqGosqws31WatOTQIoXDV9lf86ew8 +e3W6O4O42szmcX2bVekNmGQTV9fXOwIDAQABAoICAQCJAQFtoJzO22hjq4LOsfa+ +D2dd5SgUPIddm+dosO4ifwE+XXjCL1ITmkxbjVafK6URFH6tOqzdT6PrWqrN2JDX +kYXylecnEavp2glhwSilBanuiA5zxQfuZZxNZ3dUZhvmfqCK5gm066Cc31ErlEim +0PKzBmbnJ7jZBgxOX4cZpkmFLAjLBeF0sCYcLkN/bleAIW8J8xfWSclfUcVw/M7e +sE74e53FYSKVa7xRPe/Xq7xNantBkAOglvHj0AFJ1/1awiuHl+JDBtYidaEa17lj +rXOvZjY9ABTnr7f7fuajDN/uYl46blh2D0hXKNxAxvhlu4IufRCXCccqT80TLF+W +8Wlj6qysCESGTCn1xBnyriqv4ZtDgWS7gHEJqghdJr5x4q5BXpA2pWcyO5JSWrqg +kVE9rnV5j8c4hP86DNrtT4FdX9eAxMZvYmPOjF9TPOZGU7zDEpf00c7Gg9TZOlq7 +mUy7K0ybsgXQXTRDDv5prBRJ6A4mXBKZF2mQeUQPco90Wct3mAqTWPiUaCQ8+IPx +rR1BLGyTNwld8m9PxGyD8kQ3hkzMyUCh5ykqIJcRGUP4JS5xq29YJvABOxikJpRU +7QQ42k97mV7SVedPOSZFAoEhtgIbKh31OVNVulHNHac57AXwEvlLfPfmIrwSTFy6 +qElEkcFeqbxy8ZNiVJ7gMQKCAQEA3fT79JsCKtnjQSKEDdegWboW2m1jOk9f43jO +FiaaJia+i6ZuNzskOOK8F8J+C+nbWeKVRmsyCqsd+N+IB0X25PpcD1ZoXhXYBFp9 +QxANmY0LMv6d+iAv5wQ4ymQsLvjW0qdEgAKBKB7y/wnfITfbKmgm7zZzS4Qg/r6x +iPwY/1uxedJ/xC9ME7Vlo4p+pqq1pvS9ge8x51kuEB+GcmEGYBaCiVnazAZJSjcP +51EYdTQMt6nlPQu16YznlpazW8Nj5mrmxcMNbOJwphgyrOFTm3jWeaq6E3DR6Ion +FkI4hvC4f4sk5L6zVwkD5Vz0UhboZ6+VFwH7cXFN1Fh11pWkIwKCAQEAzco8VOZC +Qwwp81NEfXfuIfDRVBbTvv1W342HOSNdguWcqYDMnTE3herW4voOM4cN8tHiiybV +Ds5C3sVc+nG2mOdusP4PpQu8ZrC7eMYjFtSZjPDDBohkPuQFl1TXNvljiseM3o4m +DxQYnMxvR73zwYT6HVCY47+KqG4ll1xbqGtw2OxJFdTYgy5zqbsS6NVHqmp8Dytr +Jp5yX1sKY+uHnewJBIgzaVkQA2OFfQny5GQh1Gu3UZQGFYcxH15jeIFFQuf4FEyL +TiDGjnHOhz922Z7fGsom+vM+PrQqWhqlSAGURfnWE/9+7SQ/16nwgRPa+t7IR8m5 +qx6x/qr5svbGCQKCAQBCOyxD3U1URBhQA2CsUL+Ehsby/tkxOSY/1niIm5Q79iu9 +uDgDOx6f6uh9nofGPk46ECbDEJGqJU2YSftpUDNQOHToDryt9o6zHd1q+YtVWoUQ +/nFdheVFZjkcC7AzhAV2bns+R4EK29Fe0S9H1kcL7HBRyUm2KwM9gOGxIqjC6jWX +SHzfqc1lxCdEGbuZOC9kVnuGHj4h7raUERQpZVJlsdHZ8nobj3SnNK8GM2i88H8q +/wNsp+XsfyNMCEQVCcTxqMycDowfBaLfrTDR7ZrpNbGqNIu56Vx5q1/mgHQlsAcd +6ANmTpFtUz9aXdZ5+GP5LKesaecB/vFef9cJ5TVJAoIBAQCLWXiFHO6drCz0AdyN +AZsVDJcS0+pKmA4tjw6UGGiH7Keq9/aWMu+gFSexxxW6uqctWAaJp5/6SJ1rcEXH +qRy2aXDBFSjO4CWRe/dWjwTPvuLDV30YfV80Xv+SO/cH2NQY84tmYOdQqpEIDD8G +W5Al6L/c/eipv9hKuVtnJTlD0I03tri+ucRrABd+RZlGZLYEpdqgAwypt/1GqMVe +Z+0LePuaQAhgO9jmEowf4Y7CLTPjPZWC/jMofMU8spO01ARsi9unEzX5j6QkbNgn +KUh3kGcPIvhGUlRB7YoIPabSHY+j2sul+wqd1kAM75xWK3XLDvSb9/Nr9nsdMfWn +xAbRAoIBAQDFAiuvAuyUepnmJThP6hO9Xg90e6gD8c6zHs6ezIm8CRaOmb9FAM0l +nVz7oLsmgEuowrx1npTCzDr/RPBy94SpFbZMLzOL3cZVLd5AEDxsbt1cBUGy/Gda +dkV0J7YXeo0FVBKleM3GbpPjKgtmOMGfNmVKa3StkxvrRx4Nvro5DdhjM7CwBy7J +oNPYhCXXhvTzYvSFWQXmCJxDCKGeK7jKid2T9nlA/lCzR52m3LrBnAczTIGTPJDp +b1eyrSDuiaPs+gFn6Y94QmfNztstONlk0qRQpS+oTlmThNULJBfCJADIEXt9fVmR +flQdMlQ3lR1FtqYSypwbggaJ71j3gHU4 +-----END PRIVATE KEY-----' diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 22ecf04681..9f7f731aed 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "dotenv": "^16.4.7", - "dotenv-cli": "^8.0.0" + "dotenv-cli": "^8.0.0", + "fs-extra": "^11.3.0" } } diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 8fc8fbf075..10888bc220 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -112,7 +112,8 @@ export default defineConfig({ name: 'localElectron', testMatch: ['**.electron.spec.ts'], use: { - baseURL: 'https://chrome.desktop/', + baseURL: '/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight', + apiUrl: process.env.API_URL || 'https://localhost:5540/api', headless: false }, }, diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index dbbf9830b9..f8b08597e7 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -79,11 +79,25 @@ dotenv@^16.3.0, dotenv@^16.4.7: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== +fs-extra@^11.3.0: + version "11.3.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" + integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fsevents@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -94,6 +108,15 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -144,6 +167,11 @@ undici-types@~6.20.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From c052e8f2f12ebb02297e97274540769203e516ee Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 10:38:36 +0200 Subject: [PATCH 033/128] changes around agreement pop up --- tests/playwright/fixtures/open-ri.ts | 9 ++++----- .../pageObjects/dialogs/user-agreement-dialog.ts | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 107d243504..e2fd8aa6e9 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -71,16 +71,15 @@ export const test = base.extend< const basePage = new BasePage(page) await basePage.navigateToHomeUrl(workerState.baseUrl) - // Enter DB - const myDbPage = new MyRedisDatabasePage(page) - await myDbPage.clickOnDBByName(workerState.dbConfig.databaseName) - const userAgreementDialog = new UserAgreementDialog(page) if(await userAgreementDialog.isUserAgreementDialogVisible()){ await userAgreementDialog.acceptLicenseTerms() - await use(userAgreementDialog) } + // Enter DB + const myDbPage = new MyRedisDatabasePage(page) + await myDbPage.clickOnDBByName(workerState.dbConfig.databaseName) + await use(page) }, diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index d5d2ef9d58..235c1ad16c 100644 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -36,6 +36,6 @@ export class UserAgreementDialog extends BasePage { } async isUserAgreementDialogVisible(): Promise { - return this.isVisible(UserAgreementSelectors.userAgreementsPopup) + return await this.userAgreementsPopup.isVisible() } } From 78ef4357512fc78ac6cbe8eb797859d544bbd38e Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 11:30:28 +0200 Subject: [PATCH 034/128] app info to insights --- tests/playwright/helpers/api/api-info.ts | 14 +++++ tests/playwright/helpers/electron/insights.ts | 52 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/playwright/helpers/api/api-info.ts create mode 100755 tests/playwright/helpers/electron/insights.ts diff --git a/tests/playwright/helpers/api/api-info.ts b/tests/playwright/helpers/api/api-info.ts new file mode 100644 index 0000000000..b914f5879c --- /dev/null +++ b/tests/playwright/helpers/api/api-info.ts @@ -0,0 +1,14 @@ +import { HttpClient } from './http-client' +import { ResourcePath } from '../constants' + + +/** + * Synchronize features + */ +export async function syncFeaturesApi(apiUrl: string): Promise { + const apiClient = new HttpClient(apiUrl).getClient() + const response = await apiClient.post(ResourcePath.SyncFeatures, {}) + if (!response) { + throw new Error('Failed to synchronize features: Empty response') + } +} diff --git a/tests/playwright/helpers/electron/insights.ts b/tests/playwright/helpers/electron/insights.ts new file mode 100755 index 0000000000..66b196686c --- /dev/null +++ b/tests/playwright/helpers/electron/insights.ts @@ -0,0 +1,52 @@ +import * as fs from 'fs-extra' +import { Page } from '@playwright/test' +import * as path from 'path' +import { syncFeaturesApi } from '../api/api-info' +import { DatabaseScripts, DbTableParameters } from './database-scripts' + +const dbTableParams: DbTableParameters = { + tableName: 'features_config', + columnName: 'controlNumber', + conditionWhereColumnName: 'id', + conditionWhereColumnValue: '1' +} + +/** + * Update features-config file for static server + * @param filePath Path to feature config json + */ +export async function modifyFeaturesConfigJson(filePath: string): Promise { + const configFileName = 'features-config.json' + const remoteConfigPath = process.env.REMOTE_FOLDER_PATH || './remote' + const targetFilePath = path.join(remoteConfigPath, configFileName) + + try { + fs.ensureFileSync(targetFilePath) + fs.writeFileSync(targetFilePath, fs.readFileSync(filePath)) + } catch (err: any) { + throw new Error(`Error updating remote config file: ${err.message}`) + } +} + +/** + * Update Control Number of current user and sync + * @param controlNumber Control number to update + * @param page Playwright page instance + */ +export async function updateControlNumber(controlNumber: number, page: Page): Promise { + await syncFeaturesApi() + await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }) + await syncFeaturesApi() + await page.reload() +} + +/** + * Refresh test data for features sync + */ +export async function refreshFeaturesTestData(): Promise { + const defaultConfigPath = path.join('.', 'test-data', 'features-configs', 'insights-default-remote.json') + + await modifyFeaturesConfigJson(defaultConfigPath) + await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams) + await syncFeaturesApi() +} From 8ecc558175b5cb77a207f531e82ef3912c297025 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 11:41:28 +0200 Subject: [PATCH 035/128] moved utils and db scripts --- .../helpers/electron/database-scripts.ts | 125 +++ tests/playwright/helpers/utils.ts | 6 + tests/playwright/package.json | 3 +- tests/playwright/pageObjects/base-page.ts | 3 + tests/playwright/yarn.lock | 839 +++++++++++++++++- 5 files changed, 972 insertions(+), 4 deletions(-) create mode 100755 tests/playwright/helpers/electron/database-scripts.ts create mode 100644 tests/playwright/helpers/utils.ts diff --git a/tests/playwright/helpers/electron/database-scripts.ts b/tests/playwright/helpers/electron/database-scripts.ts new file mode 100755 index 0000000000..e21641eb36 --- /dev/null +++ b/tests/playwright/helpers/electron/database-scripts.ts @@ -0,0 +1,125 @@ +import * as sqlite3 from 'sqlite3' +import {promisify} from 'util' +import {workingDirectory} from '../conf' +import {createTimeout} from '../utils' + +const dbPath = `${workingDirectory}/redisinsight.db` + +export class DatabaseScripts { + /** + * Update table column value into local DB for a specific row + * @param dbTableParameters The sqlite database table parameters + */ + static async updateColumnValueInDBTable(dbTableParameters: DbTableParameters): Promise { + const db = new sqlite3.Database(dbPath) + try { + const runAsync = (query: string, p: (string | number | undefined)[]) => promisify(db.run.bind(db)) // convert db.run to a Promise-based function + const query = `UPDATE ${dbTableParameters.tableName} + SET ${dbTableParameters.columnName} = ? + WHERE ${dbTableParameters.conditionWhereColumnName} = ?` + await runAsync(query, [dbTableParameters.rowValue, dbTableParameters.conditionWhereColumnValue]) + } catch (err) { + console.log(`Error during changing ${dbTableParameters.columnName} column value: ${err}`) + throw new Error( + `Error during changing ${dbTableParameters.columnName} column value: ${err}`, + ) + } finally { + console.log('Close DB') + db.close() + } + + } + + /** + * Get Column value from table in local Database + * @param dbTableParameters The sqlite database table parameters + */ + static async getColumnValueFromTableInDB(dbTableParameters: DbTableParameters): Promise { + // Open the database in read/write mode and fail early if it cannot be opened. + const db = await new Promise((resolve, reject) => { + const database = new sqlite3.Database( + dbPath, + sqlite3.OPEN_READWRITE, + (err: Error | null) => { + if (err) { + reject(new Error(`Error opening DB at path ${dbPath}: ${err.message}`)) + } else { + resolve(database) + } + } + ) + }) + + const query = `SELECT ${dbTableParameters.columnName} + FROM ${dbTableParameters.tableName} + WHERE ${dbTableParameters.conditionWhereColumnName} = ?` + try { + const getAsync = (query: string, p: (string | number | undefined)[]) => promisify(db.get.bind(db)) + const row = await Promise.race([ + getAsync(query, [dbTableParameters.conditionWhereColumnValue]), + createTimeout('Query timed out after 10 seconds',10000) + ]) + if (!row) { + throw new Error(`No row found for column ${dbTableParameters.columnName}`) + } + return row[dbTableParameters.columnName!] + } catch (err: any) { + throw new Error(`Error during getting ${dbTableParameters.columnName} column value: ${err.message}`) + } finally { + db.close() + } + } + + /** + * Delete all rows from table in local DB + * @param dbTableParameters The sqlite database table parameters + */ + static async deleteRowsFromTableInDB(dbTableParameters: DbTableParameters): Promise { + const db = await new Promise((resolve, reject) => { + const database = new sqlite3.Database( + dbPath, + sqlite3.OPEN_READWRITE, + (err: Error | null) => { + if (err) { + console.log(`Error during deleteRowsFromTableInDB: ${err}`) + reject(new Error(`Error opening DB at path ${dbPath}: ${err.message}`)) + } else { + resolve(database) + } + } + ) + }) + + const query = `DELETE + FROM ${dbTableParameters.tableName}` + + try { + const runAsync = promisify(db.run.bind(db)) + await Promise.race([ + runAsync(query), + createTimeout('DELETE operation timed out after 10 seconds', 10000) + ]) + } catch (err: any) { + throw new Error(`Error during ${dbTableParameters.tableName} table rows deletion: ${err.message}`) + } finally { + db.close() + } + } + +} + +/** + * Add new database parameters + * @param tableName The name of table in DB + * @param columnName The name of column in table + * @param rowValue Value to update in table + * @param conditionWhereColumnName The name of the column to search + * @param conditionWhereColumnValue The value to match in the column + */ +export type DbTableParameters = { + tableName: string, + columnName?: string, + rowValue?: string | number, + conditionWhereColumnName?: string, + conditionWhereColumnValue?: string +} diff --git a/tests/playwright/helpers/utils.ts b/tests/playwright/helpers/utils.ts new file mode 100644 index 0000000000..053db8ef19 --- /dev/null +++ b/tests/playwright/helpers/utils.ts @@ -0,0 +1,6 @@ +export const createTimeout = (errorMessage: string, timeout: number): Promise => + new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(errorMessage)); + }, timeout); + }); diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 9f7f731aed..a5b1696372 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -26,6 +26,7 @@ "dependencies": { "dotenv": "^16.4.7", "dotenv-cli": "^8.0.0", - "fs-extra": "^11.3.0" + "fs-extra": "^11.3.0", + "sqlite3": "^5.1.7" } } diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 6c694005e2..b89b2f3d95 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -44,4 +44,7 @@ export default class BasePage { async waitForLocatorNotVisible(locator: Locator, timeout = 6000) { await expect(locator).not.toBeVisible({ timeout }) } + async goBackHistor(): Promise{ + await this.page.goBack() + }; } diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index f8b08597e7..b722e6e3a9 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -7,6 +7,27 @@ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d" integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw== +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@playwright/test@^1.51.0": version "1.51.0" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.0.tgz#8d5c8400b465a0bfdbcf993e390ceecb903ea6d2" @@ -14,6 +35,11 @@ dependencies: playwright "1.51.0" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@types/node@^22.13.10": version "22.13.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" @@ -21,6 +47,33 @@ dependencies: undici-types "~6.20.0" +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + allure-commandline@^2.33.0: version "2.33.0" resolved "https://registry.yarnpkg.com/allure-commandline/-/allure-commandline-2.33.0.tgz#140560c615ea904ff34c061c4c4b6d43858b2b68" @@ -40,11 +93,125 @@ allure-playwright@^3.2.0: dependencies: allure-js-commons "3.2.0" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + charenc@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -59,6 +226,35 @@ crypt@0.0.2: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +debug@4, debug@^4.3.3: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + dotenv-cli@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-8.0.0.tgz#cea1519f5a06c7372a1428fca4605fcf3d50e1cf" @@ -79,6 +275,50 @@ dotenv@^16.3.0, dotenv@^16.4.7: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^11.3.0: version "11.3.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" @@ -88,26 +328,171 @@ fs-extra@^11.3.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + fsevents@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -graceful-fs@^4.1.6, graceful-fs@^4.2.0: +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +http-cache-semantics@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -117,6 +502,35 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -126,11 +540,171 @@ md5@^2.3.0: crypt "0.0.2" is-buffer "~1.1.6" -minimist@^1.2.6: +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@^2.0.0, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +napi-build-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== + +negotiator@^0.6.2: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + +node-abi@^3.3.0: + version "3.74.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.74.0.tgz#5bfb4424264eaeb91432d2adb9da23c63a301ed0" + integrity sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w== + dependencies: + semver "^7.3.5" + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +once@^1.3.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" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -150,6 +724,96 @@ playwright@1.51.0: optionalDependencies: fsevents "2.3.2" +prebuild-install@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^2.0.0" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.3.5: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -162,19 +826,188 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.4" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.4.tgz#07109755cdd4da03269bda4725baa061ab56d5cc" + integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sqlite3@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +tar-fs@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5" + integrity sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + undici-types@~6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -which@^2.0.1: +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" + +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== From 475ed5373ae35c918be80ba6f08ecc87d060c157 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 12:40:27 +0200 Subject: [PATCH 036/128] closing app --- tests/playwright/fixtures/electron.ts | 37 +++++++------------ tests/playwright/helpers/electron/insights.ts | 10 ++--- .../playwright/tests/example.electron.spec.ts | 6 ++- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts index e92acad78e..1680bf5919 100644 --- a/tests/playwright/fixtures/electron.ts +++ b/tests/playwright/fixtures/electron.ts @@ -7,11 +7,12 @@ type WorkerSharedState = { apiUrl: string; dbConfig: typeof ossStandaloneConfig; baseUrl: string; + electronApp: ElectronApplication; } type ElectronFixture = { electronApp: ElectronApplication; - mainWindow: Page; + electronPage: Page; } // /home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage // /home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight @@ -35,31 +36,30 @@ export const test = base.extend { - const ti = base.info().workerIndex - console.log(`BEFORE Starting test worker ${ti}`) + // Set up the database before tests - const dbApi = new DatabaseAPIRequests(workerState.apiUrl) - await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) + // const dbApi = new DatabaseAPIRequests(workerState.apiUrl) + // await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) await use() // Run the tests - // Something failing here doesn't affect test execution result - console.log(`Stopping test worker ${ti}`) + // Cleanup after all tests in this worker // throw new Error("test worker error") - await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) - + // await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + //close the app + await workerState.electronApp.close() }, { scope: 'worker', auto: true }], - electronApp: async ({baseURL}, use) => { + electronApp: async ({baseURL, workerState}, use) => { // Launch Electron App const electronApp = await electron.launch({ executablePath: baseURL, args: ['index.html'] // Adjust the path to your Electron main entry file }) - + workerState.electronApp = electronApp // Evaluation expression in the Electron context. const appPath = await electronApp.evaluate(async ({ app }) => // This runs in the main Electron process, parameter here is always @@ -68,26 +68,15 @@ export const test = base.extend { + electronPage: async ({ electronApp }, use) => { // Get the first window of the Electron app const window = await electronApp.firstWindow() console.log('IN MAIN WINDOW') diff --git a/tests/playwright/helpers/electron/insights.ts b/tests/playwright/helpers/electron/insights.ts index 66b196686c..224751b02a 100755 --- a/tests/playwright/helpers/electron/insights.ts +++ b/tests/playwright/helpers/electron/insights.ts @@ -33,20 +33,20 @@ export async function modifyFeaturesConfigJson(filePath: string): Promise * @param controlNumber Control number to update * @param page Playwright page instance */ -export async function updateControlNumber(controlNumber: number, page: Page): Promise { - await syncFeaturesApi() +export async function updateControlNumber(controlNumber: number, page: Page, apiUrl: string): Promise { + await syncFeaturesApi(apiUrl) await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }) - await syncFeaturesApi() + await syncFeaturesApi(apiUrl) await page.reload() } /** * Refresh test data for features sync */ -export async function refreshFeaturesTestData(): Promise { +export async function refreshFeaturesTestData(apiUrl: string): Promise { const defaultConfigPath = path.join('.', 'test-data', 'features-configs', 'insights-default-remote.json') await modifyFeaturesConfigJson(defaultConfigPath) await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams) - await syncFeaturesApi() + await syncFeaturesApi(apiUrl) } diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 89c01df209..a03f4c48f0 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,17 +1,19 @@ import {test, expect} from '../fixtures/electron' // import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' +import {UserAgreementDialog} from "../pageObjects/dialogs/user-agreement-dialog"; // import {APIKeyRequests} from "../helpers/api/api-keys"; let keyName: string let browserPage: BrowserPage - +let userAgent: UserAgreementDialog test.beforeEach(async ({electronApp}) => { - console.log('WE ARE IN THE BEFORE STEP') + userAgent // keyName = Common.generateAlpanumeric(10) // browserPage = new BrowserPage(basePage) + }) test.afterEach(async ({electronApp}) => { From c97df523584fe9d649bcc25a898d1561552ef8d8 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 13:41:42 +0200 Subject: [PATCH 037/128] more page objects and methods --- tests/playwright/fixtures/electron.ts | 4 +- tests/playwright/fixtures/open-ri.ts | 5 +- tests/playwright/pageObjects/base-page.ts | 9 + .../dialogs/add-rdi-instance-dialog.ts | 61 ++++++ .../pageObjects/rdi-instances-list-page.ts | 178 ++++++++++++++++++ .../playwright/tests/example.electron.spec.ts | 21 ++- 6 files changed, 269 insertions(+), 9 deletions(-) create mode 100755 tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts create mode 100755 tests/playwright/pageObjects/rdi-instances-list-page.ts diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts index 1680bf5919..069e47036f 100644 --- a/tests/playwright/fixtures/electron.ts +++ b/tests/playwright/fixtures/electron.ts @@ -78,10 +78,10 @@ export const test = base.extend { // Get the first window of the Electron app - const window = await electronApp.firstWindow() + const page = await electronApp.firstWindow() console.log('IN MAIN WINDOW') - await use(window) + await use(page) }, }) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index e2fd8aa6e9..9ca138daf0 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -72,9 +72,8 @@ export const test = base.extend< await basePage.navigateToHomeUrl(workerState.baseUrl) const userAgreementDialog = new UserAgreementDialog(page) - if(await userAgreementDialog.isUserAgreementDialogVisible()){ - await userAgreementDialog.acceptLicenseTerms() - } + await userAgreementDialog.acceptLicenseTerms() + // Enter DB const myDbPage = new MyRedisDatabasePage(page) diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index b89b2f3d95..cb4eca7666 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -47,4 +47,13 @@ export default class BasePage { async goBackHistor(): Promise{ await this.page.goBack() }; + async elementExistsSelector( selector: string): Promise { + const count = await this.page.locator(selector).count() + return count > 0 + } + + async elementExistsLocator( locator: Locator): Promise { + const count = await locator.count() + return count > 0 + } } diff --git a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts new file mode 100755 index 0000000000..692a3d9771 --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts @@ -0,0 +1,61 @@ +import { Page, Locator } from '@playwright/test' + +export class AddRdiInstanceDialog { + readonly page: Page + + // INPUTS + readonly rdiAliasInput: Locator + + readonly urlInput: Locator + + readonly usernameInput: Locator + + readonly passwordInput: Locator + + // BUTTONS + readonly addInstanceButton: Locator + + readonly cancelInstanceBtn: Locator + + readonly connectToRdiForm: Locator + + // ICONS + readonly urlInputInfoIcon: Locator + + readonly usernameInputInfoIcon: Locator + + readonly passwordInputInfoIcon: Locator + + constructor(page: Page) { + this.page = page + this.rdiAliasInput = page.getByTestId('connection-form-name-input') + this.urlInput = page.getByTestId('connection-form-url-input') + this.usernameInput = page.getByTestId('connection-form-username-input') + this.passwordInput = page.getByTestId('connection-form-password-input') + + this.addInstanceButton = page.getByTestId('connection-form-add-button') + this.cancelInstanceBtn = page.getByTestId('connection-form-cancel-button') + this.connectToRdiForm = page.getByTestId('connection-form') + + // Assuming that the two-level parent traversal is needed. + // Using an XPath locator to navigate two ancestors then find an SVG element. + this.urlInputInfoIcon = page + .getByTestId('connection-form-url-input') + .locator('xpath=ancestor::div[2]//svg') + this.usernameInputInfoIcon = page + .getByTestId('connection-form-username-input') + .locator('xpath=ancestor::div[2]//svg') + this.passwordInputInfoIcon = page + .getByTestId('connection-form-password-input') + .locator('xpath=ancestor::div[2]//svg') + } +} + +export type RdiInstance = { + alias: string; + url: string; + version?: string; + lastConnection?: string; + username?: string; + password?: string; +} diff --git a/tests/playwright/pageObjects/rdi-instances-list-page.ts b/tests/playwright/pageObjects/rdi-instances-list-page.ts new file mode 100755 index 0000000000..ee501ee81b --- /dev/null +++ b/tests/playwright/pageObjects/rdi-instances-list-page.ts @@ -0,0 +1,178 @@ +import { Page, Locator, expect } from '@playwright/test' +import { BaseOverviewPage } from './base-overview-page' +import { AddRdiInstanceDialog, RdiInstance } from './dialogs/add-rdi-instance-dialog' + +export class RdiInstancesListPage extends BaseOverviewPage { + readonly page: Page + + // readonly AddRdiInstanceDialog: AddRdiInstanceDialog + + readonly addRdiInstanceButton: Locator + + readonly addRdiFromEmptyListBtn: Locator + + readonly quickstartBtn: Locator + + readonly rdiInstanceRow: Locator + + readonly emptyRdiList: Locator + + readonly rdiNameList: Locator + + readonly searchInput: Locator + + readonly sortBy: Locator + + readonly cssRdiAlias: string + + readonly cssUrl: string + + readonly cssRdiVersion: string + + readonly cssLastConnection: string + + // Assuming these selectors exist—update their locators as needed. + readonly deleteRowButton: Locator + + readonly confirmDeleteButton: Locator + + readonly editRowButton: Locator + + readonly Toast: { toastCloseButton: Locator } + + constructor(page: Page) { + super(page) + this.page = page + + // this.AddRdiInstanceDialog = new AddRdiInstanceDialog(page) + + // Use getByTestId for selectors with data-testid + this.addRdiInstanceButton = page.getByTestId('rdi-instance') + this.addRdiFromEmptyListBtn = page.getByTestId('empty-rdi-instance-button') + this.quickstartBtn = page.getByTestId('empty-rdi-quickstart-button') + + this.rdiInstanceRow = page.locator('[class*=euiTableRow-isSelectable]') + this.emptyRdiList = page.getByTestId('empty-rdi-instance-list') + this.rdiNameList = page.locator('[class*=column_name] div') + + this.searchInput = page.getByTestId('search-rdi-instance-list') + + // Selector using data-test-subj remains as locator + this.sortBy = page.locator('[data-test-subj=tableHeaderSortButton] span') + + // CSS selectors (kept as string constants) + this.cssRdiAlias = '[data-test-subj=rdi-alias-column]' + this.cssUrl = '[data-testid=url]' + this.cssRdiVersion = '[data-test-subj=rdi-instance-version-column]' + this.cssLastConnection = '[data-test-subj=rdi-instance-last-connection-column]' + + // These selectors are assumed. Adjust the test IDs as per your application. + this.deleteRowButton = page.getByTestId('delete-row-button') + this.confirmDeleteButton = page.getByTestId('confirm-delete-button') + this.editRowButton = page.getByTestId('edit-row-button') + this.Toast = { + toastCloseButton: page.getByTestId('toast-close-button') + } + } + + // /** + // * Add Rdi instance. + // * @param instanceValue Rdi instance data + // */ + // async addRdi(instanceValue: RdiInstance): Promise { + // await this.page.click(this.addRdiInstanceButton) + // await this.page.fill(this.AddRdiInstanceDialog.rdiAliasInput, instanceValue.alias) + // await this.page.fill(this.AddRdiInstanceDialog.urlInput, instanceValue.url) + // await this.page.fill(this.AddRdiInstanceDialog.usernameInput, instanceValue.username) + // await this.page.fill(this.AddRdiInstanceDialog.passwordInput, instanceValue.password) + // await this.page.click(this.AddRdiInstanceDialog.addInstanceButton) + // } + + /** + * Get Rdi instance values by index. + * @param index Index of Rdi instance. + */ + async getRdiInstanceValuesByIndex(index: number): Promise { + const alias = await this.rdiInstanceRow.nth(index).locator(this.cssRdiAlias).innerText() + const currentLastConnection = await this.rdiInstanceRow.nth(0).locator(this.cssLastConnection).innerText() + const currentVersion = await this.rdiInstanceRow.nth(0).locator(this.cssRdiVersion).innerText() + const currentUrl = await this.rdiInstanceRow.nth(0).locator(this.cssUrl).innerText() + + const rdiInstance: RdiInstance = { + alias, + url: currentUrl, + version: currentVersion, + lastConnection: currentLastConnection, + } + + return rdiInstance + } + + /** + * Delete Rdi by name. + * @param dbName The name of the Rdi to be deleted. + */ + async deleteRdiByName(dbName: string): Promise { + const dbNames = this.rdiInstanceRow + const count = await dbNames.count() + for (let i = 0; i < count; i++) { + const text = await dbNames.nth(i).innerText() + if (text.includes(dbName)) { + await this.page.click(this.deleteRowButton.nth(i)) + await this.page.click(this.confirmDeleteButton) + break + } + } + } + + /** + * Edit Rdi by name. + * @param dbName The name of the Rdi to be edited. + */ + async clickEditRdiByName(dbName: string): Promise { + const rdiNames = this.rdiInstanceRow + const count = await rdiNames.count() + for (let i = 0; i < count; i++) { + const text = await rdiNames.nth(i).innerText() + if (text.includes(dbName)) { + await this.page.click(this.editRowButton.nth(i)) + break + } + } + } + + /** + * Click Rdi by name. + * @param rdiName The name of the Rdi. + */ + async clickRdiByName(rdiName: string): Promise { + if (await this.Toast.toastCloseButton.isVisible()) { + await this.page.click(this.Toast.toastCloseButton) + } + // Use getByText with exact match for the Rdi name + const rdi = this.rdiNameList.getByText(rdiName.trim(), { exact: true }) + await expect(rdi).toBeVisible({ timeout: 3000 }) + await rdi.click() + } + + /** + * Sort Rdi list by column. + * @param columnName The name of the column. + */ + async sortByColumn(columnName: string): Promise { + await this.page.click(this.sortBy.filter({ hasText: columnName })) + } + + /** + * Get all Rdi aliases. + */ + async getAllRdiNames(): Promise { + const rdis: string[] = [] + const count = await this.rdiInstanceRow.count() + for (let i = 0; i < count; i++) { + const name = await this.rdiInstanceRow.nth(i).locator(this.cssRdiAlias).innerText() + rdis.push(name) + } + return rdis + } +} diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index a03f4c48f0..9233fc703b 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,15 +1,28 @@ import {test, expect} from '../fixtures/electron' // import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' -import {UserAgreementDialog} from "../pageObjects/dialogs/user-agreement-dialog"; +import {UserAgreementDialog} from '../pageObjects/dialogs/user-agreement-dialog' +import {updateControlNumber} from '../helpers/electron/insights' +import {RedisOverviewPage} from '../helpers/constants' +import {RdiInstancesListPage} from '../pageObjects/rdi-instances-list-page' // import {APIKeyRequests} from "../helpers/api/api-keys"; let keyName: string let browserPage: BrowserPage -let userAgent: UserAgreementDialog -test.beforeEach(async ({electronApp}) => { +let userAgreementDialog: UserAgreementDialog +let rdiInstancesListPage : RdiInstancesListPage +test.beforeEach(async ({electronPage, workerState}) => { + + rdiInstancesListPage = new RdiInstancesListPage(electronPage) + userAgreementDialog = new UserAgreementDialog(electronPage) + + await userAgreementDialog.acceptLicenseTerms() + await updateControlNumber(48.2, electronPage, workerState.apiUrl) +// Open default databases list tab if RDI opened + if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { + await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) + } - userAgent // keyName = Common.generateAlpanumeric(10) // browserPage = new BrowserPage(basePage) From b5fc565c83371d82031c81fcc68aee44cff17726 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 14:46:02 +0200 Subject: [PATCH 038/128] more pages and changes --- tests/playwright/helpers/database.ts | 736 +++++++++++++----- ...uto-discover-redis-enterprise-databases.ts | 30 + .../pageObjects/base-overview-page.ts | 2 +- tests/playwright/pageObjects/base-page.ts | 9 +- tests/playwright/pageObjects/browser-page.ts | 2 +- .../pageObjects/components/common/toast.ts | 2 +- .../components/redis-cloud-sign-in-panel.ts | 4 +- .../dialogs/add-rdi-instance-dialog.ts | 4 +- .../dialogs/add-redis-database-dialog.ts | 5 +- .../dialogs/user-agreement-dialog.ts | 2 +- tests/playwright/pageObjects/index.ts | 9 + 11 files changed, 613 insertions(+), 192 deletions(-) create mode 100755 tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts create mode 100644 tests/playwright/pageObjects/index.ts diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 0bc28a5589..0e4496458c 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -1,20 +1,475 @@ -// import { Selector, t } from 'testcafe'; -import { DatabaseAPIRequests } from './api/api-database' +// // import { Selector, t } from 'testcafe'; +// import { DatabaseAPIRequests } from './api/api-database' +// import { RedisOverviewPage } from './constants' +// import { updateControlNumber } from './insights' +// import { +// AddNewDatabaseParameters, +// SentinelParameters, +// OSSClusterParameters +// } from '../pageObjects/dialogs/add-redis-database-dialog' +// import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' +// import { +// MyRedisDatabasePage, +// BrowserPage, +// AutoDiscoverREDatabases +// } from '../pageObjects' +// import { UserAgreementDialog } from '../pageObjects/dialogs' +// import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' +// +// const myRedisDatabasePage = new MyRedisDatabasePage() +// const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() +// const autoDiscoverREDatabases = new AutoDiscoverREDatabases() +// const browserPage = new BrowserPage() +// const userAgreementDialog = new UserAgreementDialog() +// const databaseAPIRequests = new DatabaseAPIRequests() +// const rdiInstancesListPage = new RdiInstancesListPage() +// +// export class DatabaseHelper { +// /** +// * Add a new database manually using host and port +// * @param databaseParameters The database parameters +// */ +// async addNewStandaloneDatabase( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// // Fill the add database form +// await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( +// databaseParameters +// ) +// // Click for saving +// await t +// .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) +// // Wait for database to be exist +// .expect( +// myRedisDatabasePage.dbNameList.withExactText( +// databaseParameters.databaseName ?? '' +// ).exists +// ) +// .ok('The database not displayed', { timeout: 10000 }) +// // Close message +// .click(myRedisDatabasePage.Toast.toastCloseButton) +// } +// +// /** +// * Add a new database via autodiscover using Sentinel option +// * @param databaseParameters The Sentinel parameters: host, port and sentinel password +// */ +// async discoverSentinelDatabase( +// databaseParameters: SentinelParameters +// ): Promise { +// // Fill sentinel parameters to auto-discover Master Groups +// await myRedisDatabasePage.AddRedisDatabaseDialog.discoverSentinelDatabases( +// databaseParameters +// ) +// // Click for autodiscover +// await t +// .click( +// myRedisDatabasePage.AddRedisDatabaseDialog +// .addRedisDatabaseButton +// ) +// .expect(discoverMasterGroupsPage.addPrimaryGroupButton.exists) +// .ok('User is not on the second step of Sentinel flow', { +// timeout: 10000 +// }) +// // Select Master Groups and Add to Redis Insight +// await discoverMasterGroupsPage.addMasterGroups() +// await t.click(autoDiscoverREDatabases.viewDatabasesButton) +// } +// +// /** +// * Add a new database from RE Cluster via auto-discover flow +// * @param databaseParameters The database parameters +// */ +// async addNewREClusterDatabase( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// // Fill the add database form +// await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverREClusterDatabase( +// databaseParameters +// ) +// // Click on submit button +// await t +// .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) +// // Wait for database to be exist in the list of Autodiscover databases and select it +// .expect( +// autoDiscoverREDatabases.databaseName.withExactText( +// databaseParameters.databaseName ?? '' +// ).exists +// ) +// .ok('The database not displayed', { timeout: 10000 }) +// .typeText( +// autoDiscoverREDatabases.search, +// databaseParameters.databaseName ?? '' +// ) +// .click(autoDiscoverREDatabases.databaseCheckbox) +// // Click Add selected databases button +// .click(autoDiscoverREDatabases.addSelectedDatabases) +// .click(autoDiscoverREDatabases.viewDatabasesButton) +// } +// +// /** +// * Add a new database from OSS Cluster via auto-discover flow +// * @param databaseParameters The database parameters +// */ +// async addOSSClusterDatabase( +// databaseParameters: OSSClusterParameters +// ): Promise { +// // Enter required parameters for OSS Cluster +// await myRedisDatabasePage.AddRedisDatabaseDialog.addOssClusterDatabase( +// databaseParameters +// ) +// // Click for saving +// await t +// .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) +// // Check for info message that DB was added +// .expect(myRedisDatabasePage.Toast.toastHeader.exists) +// .ok('Info message not exists', { timeout: 10000 }) +// // Wait for database to be exist +// .expect( +// myRedisDatabasePage.dbNameList.withExactText( +// databaseParameters.ossClusterDatabaseName +// ).exists +// ) +// .ok('The database not displayed', { timeout: 10000 }) +// } +// +// /** +// * Add a new database from Redis Cloud via auto-discover flow +// * @param cloudAPIAccessKey The Cloud API Access Key +// * @param cloudAPISecretKey The Cloud API Secret Key +// */ +// async autodiscoverRECloudDatabase( +// cloudAPIAccessKey: string, +// cloudAPISecretKey: string +// ): Promise { +// // Fill the add database form and Submit +// await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverRECloudDatabase( +// cloudAPIAccessKey, +// cloudAPISecretKey +// ) +// await t.click( +// myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton +// ) +// await t +// .expect( +// autoDiscoverREDatabases.title.withExactText( +// 'Redis Cloud Subscriptions' +// ).exists +// ) +// .ok('Subscriptions list not displayed', { timeout: 120000 }) +// // Select subscriptions +// await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox) +// await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton) +// // Select databases for adding +// const databaseName = await autoDiscoverREDatabases.getDatabaseName() +// await t.click(autoDiscoverREDatabases.databaseCheckbox) +// await t.click(autoDiscoverREDatabases.addSelectedDatabases) +// // Wait for database to be exist in the redis databases list +// await t +// .expect( +// autoDiscoverREDatabases.title.withExactText( +// 'Redis Enterprise Databases Added' +// ).exists +// ) +// .ok('Added databases list not displayed', { timeout: 20000 }) +// await t.click(autoDiscoverREDatabases.viewDatabasesButton) +// // uncomment when fixed db will be added to cloud subscription +// // await t.expect(myRedisDatabasePage.dbNameList.withExactText(databaseName).exists).ok('The database not displayed', { timeout: 10000 }); +// return databaseName +// } +// +// /** +// * Accept License terms and add database +// * @param databaseParameters The database parameters +// * @param databaseName The database name +// */ +// async acceptLicenseTermsAndAddDatabase( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// await this.acceptLicenseTerms() +// await this.addNewStandaloneDatabase(databaseParameters) +// // Connect to DB +// await myRedisDatabasePage.clickOnDBByName( +// databaseParameters.databaseName! +// ) +// } +// +// /** +// * Accept License terms and add database using api +// * @param databaseParameters The database parameters +// * @param databaseName The database name +// */ +// async acceptLicenseTermsAndAddDatabaseApi( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// await this.acceptLicenseTerms() +// await databaseAPIRequests.addNewStandaloneDatabaseApi( +// databaseParameters +// ) +// // Reload Page to see the new added database through api +// await myRedisDatabasePage.reloadPage() +// // Connect to DB +// await myRedisDatabasePage.clickOnDBByName( +// databaseParameters.databaseName! +// ) +// } +// +// /** +// * Accept License terms and add OSS cluster database +// * @param databaseParameters The database parameters +// * @param databaseName The database name +// */ +// async acceptLicenseTermsAndAddOSSClusterDatabase( +// databaseParameters: OSSClusterParameters +// ): Promise { +// await this.acceptLicenseTerms() +// await this.addOSSClusterDatabase(databaseParameters) +// // Connect to DB +// await myRedisDatabasePage.clickOnDBByName( +// databaseParameters.ossClusterDatabaseName! +// ) +// } +// +// /** +// * Accept License terms and add Sentinel database using api +// * @param databaseParameters The database parameters +// */ +// async acceptLicenseTermsAndAddSentinelDatabaseApi( +// databaseParameters: SentinelParameters +// ): Promise { +// await this.acceptLicenseTerms() +// await databaseAPIRequests.discoverSentinelDatabaseApi( +// databaseParameters +// ) +// // Reload Page to see the database added through api +// await myRedisDatabasePage.reloadPage() +// // Connect to DB +// await myRedisDatabasePage.clickOnDBByName( +// databaseParameters.masters![1].alias ?? '' +// ) +// } +// +// /** +// * Accept License terms and add RE Cluster database +// * @param databaseParameters The database parameters +// */ +// async acceptLicenseTermsAndAddREClusterDatabase( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// await this.acceptLicenseTerms() +// await this.addNewREClusterDatabase(databaseParameters) +// // Connect to DB +// await myRedisDatabasePage.clickOnDBByName( +// databaseParameters.databaseName ?? '' +// ) +// } +// +// /** +// * Accept License terms and add RE Cloud database +// * @param databaseParameters The database parameters +// */ +// async acceptLicenseTermsAndAddRECloudDatabase( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// const searchTimeout = 60 * 1000 // 60 sec to wait database appearing +// const dbSelector = myRedisDatabasePage.dbNameList.withExactText( +// databaseParameters.databaseName ?? '' +// ) +// const startTime = Date.now() +// +// await this.acceptLicenseTerms() +// await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( +// databaseParameters +// ) +// // Click for saving +// await t.click( +// myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton +// ) +// await t.wait(3000) +// // Reload page until db appears +// do { +// await myRedisDatabasePage.reloadPage() +// } while ( +// !(await dbSelector.exists) && +// Date.now() - startTime < searchTimeout +// ) +// await t +// .expect( +// myRedisDatabasePage.dbNameList.withExactText( +// databaseParameters.databaseName ?? '' +// ).exists +// ) +// .ok('The database not displayed', { timeout: 5000 }) +// await myRedisDatabasePage.clickOnDBByName( +// databaseParameters.databaseName ?? '' +// ) +// await t +// .expect(browserPage.keysSummary.exists) +// .ok('Key list not loaded', { timeout: 15000 }) +// } +// +// /** +// * Add RE Cloud database +// * @param databaseParameters The database parameters +// */ +// async addRECloudDatabase( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// const searchTimeout = 60 * 1000 // 60 sec to wait database appearing +// const dbSelector = myRedisDatabasePage.dbNameList.withExactText( +// databaseParameters.databaseName ?? '' +// ) +// const startTime = Date.now() +// +// await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( +// databaseParameters +// ) +// // Click for saving +// await t.click( +// myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton +// ) +// await t.wait(3000) +// // Reload page until db appears +// do { +// await myRedisDatabasePage.reloadPage() +// } while ( +// !(await dbSelector.exists) && +// Date.now() - startTime < searchTimeout +// ) +// await t +// .expect( +// myRedisDatabasePage.dbNameList.withExactText( +// databaseParameters.databaseName ?? '' +// ).exists +// ) +// .ok('The database not displayed', { timeout: 5000 }) +// } +// +// // Accept License terms +// async acceptLicenseTerms(): Promise { +// // await t.maximizeWindow() +// await userAgreementDialog.acceptLicenseTerms() +// await updateControlNumber(48.2) +// // Open default databases list tab if RDI opened +// if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { +// await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) +// } +// // TODO delete after releasing chatbot +// if (await myRedisDatabasePage.AddRedisDatabaseDialog.aiChatMessage.exists) { +// await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.aiCloseMessage) +// } +// } +// +// // Accept License terms and connect to the RedisStack database +// async acceptLicenseAndConnectToRedisStack(): Promise { +// await this.acceptLicenseTerms() +// // Connect to DB +// await t +// .click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) +// .click( +// myRedisDatabasePage.AddRedisDatabaseDialog.connectToRedisStackButton +// ) +// } +// +// /** +// * Delete database +// * @param databaseName The database name +// */ +// async deleteDatabase(databaseName: string): Promise { +// await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) +// if ( +// await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists +// ) { +// await this.deleteDatabaseByNameApi(databaseName) +// } +// } +// +// /** +// * Delete database with custom name +// * @param databaseName The database name +// */ +// async deleteCustomDatabase(databaseName: string): Promise { +// await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) +// if ( +// await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists +// ) { +// await myRedisDatabasePage.deleteDatabaseByName(databaseName) +// } +// } +// +// /** +// * Accept License terms and add database or connect to the Redis stask database +// * @param databaseParameters The database parameters +// * @param databaseName The database name +// */ +// async acceptTermsAddDatabaseOrConnectToRedisStack( +// databaseParameters: AddNewDatabaseParameters +// ): Promise { +// if ( +// await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists +// ) { +// await this.acceptLicenseTermsAndAddDatabase(databaseParameters) +// } +// else { +// await this.acceptLicenseAndConnectToRedisStack() +// } +// } +// +// /** +// * Click on the edit database button by name +// * @param databaseName The name of the database +// */ +// async clickOnEditDatabaseByName(databaseName: string): Promise { +// const databaseId = await databaseAPIRequests.getDatabaseIdByName( +// databaseName +// ) +// const databaseEditBtn = Selector( +// `[data-testid=edit-instance-${databaseId}]` +// ) +// +// await t +// .expect(databaseEditBtn.exists) +// .ok(`"${databaseName}" database not displayed`) +// await t.click(databaseEditBtn) +// } +// +// /** +// * Delete database button by name +// * @param databaseName The name of the database +// */ +// async deleteDatabaseByNameApi(databaseName: string): Promise { +// const databaseId = await databaseAPIRequests.getDatabaseIdByName( +// databaseName +// ) +// const databaseDeleteBtn = Selector( +// `[data-testid=delete-instance-${databaseId}-icon]` +// ) +// +// await t +// .expect(databaseDeleteBtn.exists) +// .ok(`"${databaseName}" database not displayed`) +// await t.click(databaseDeleteBtn) +// await t.click(myRedisDatabasePage.confirmDeleteButton) +// } +// } +import { expect } from '@playwright/test' +import { DatabaseAPIRequests } from './api/api-databases' import { RedisOverviewPage } from './constants' -import { updateControlNumber } from './insights' +import { updateControlNumber } from './electron/insights' import { AddNewDatabaseParameters, SentinelParameters, OSSClusterParameters -} from '../pageObjects/dialogs/add-redis-database-dialog' +} from '../types' +import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialog' +import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' import { MyRedisDatabasePage, BrowserPage, AutoDiscoverREDatabases } from '../pageObjects' -import { UserAgreementDialog } from '../pageObjects/dialogs' -import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' + const myRedisDatabasePage = new MyRedisDatabasePage() const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() @@ -37,17 +492,13 @@ export class DatabaseHelper { databaseParameters ) // Click for saving - await t - .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) - // Wait for database to be exist - .expect( - myRedisDatabasePage.dbNameList.withExactText( - databaseParameters.databaseName ?? '' - ).exists - ) - .ok('The database not displayed', { timeout: 10000 }) - // Close message - .click(myRedisDatabasePage.Toast.toastCloseButton) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + // Wait for database to exist + await expect( + myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + ).toBeVisible({ timeout: 10000 }) + // Close message + await myRedisDatabasePage.Toast.toastCloseButton.click() } /** @@ -62,18 +513,13 @@ export class DatabaseHelper { databaseParameters ) // Click for autodiscover - await t - .click( - myRedisDatabasePage.AddRedisDatabaseDialog - .addRedisDatabaseButton - ) - .expect(discoverMasterGroupsPage.addPrimaryGroupButton.exists) - .ok('User is not on the second step of Sentinel flow', { - timeout: 10000 - }) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await expect(discoverMasterGroupsPage.addPrimaryGroupButton).toBeVisible({ + timeout: 10000 + }) // Select Master Groups and Add to Redis Insight await discoverMasterGroupsPage.addMasterGroups() - await t.click(autoDiscoverREDatabases.viewDatabasesButton) + await autoDiscoverREDatabases.viewDatabasesButton.click() } /** @@ -88,23 +534,16 @@ export class DatabaseHelper { databaseParameters ) // Click on submit button - await t - .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) - // Wait for database to be exist in the list of Autodiscover databases and select it - .expect( - autoDiscoverREDatabases.databaseName.withExactText( - databaseParameters.databaseName ?? '' - ).exists - ) - .ok('The database not displayed', { timeout: 10000 }) - .typeText( - autoDiscoverREDatabases.search, - databaseParameters.databaseName ?? '' - ) - .click(autoDiscoverREDatabases.databaseCheckbox) - // Click Add selected databases button - .click(autoDiscoverREDatabases.addSelectedDatabases) - .click(autoDiscoverREDatabases.viewDatabasesButton) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + // Wait for database to exist in the list of Autodiscover databases and select it + await expect( + autoDiscoverREDatabases.databaseName.getByText(databaseParameters.databaseName ?? '', { exact: true }) + ).toBeVisible({ timeout: 10000 }) + await autoDiscoverREDatabases.search.fill(databaseParameters.databaseName ?? '') + await autoDiscoverREDatabases.databaseCheckbox.click() + // Click Add selected databases button + await autoDiscoverREDatabases.addSelectedDatabases.click() + await autoDiscoverREDatabases.viewDatabasesButton.click() } /** @@ -119,18 +558,13 @@ export class DatabaseHelper { databaseParameters ) // Click for saving - await t - .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) - // Check for info message that DB was added - .expect(myRedisDatabasePage.Toast.toastHeader.exists) - .ok('Info message not exists', { timeout: 10000 }) - // Wait for database to be exist - .expect( - myRedisDatabasePage.dbNameList.withExactText( - databaseParameters.ossClusterDatabaseName - ).exists - ) - .ok('The database not displayed', { timeout: 10000 }) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + // Check for info message that DB was added + await expect(myRedisDatabasePage.Toast.toastHeader).toBeVisible({ timeout: 10000 }) + // Wait for database to exist + await expect( + myRedisDatabasePage.dbNameList.getByText(databaseParameters.ossClusterDatabaseName, { exact: true }) + ).toBeVisible({ timeout: 10000 }) } /** @@ -147,34 +581,24 @@ export class DatabaseHelper { cloudAPIAccessKey, cloudAPISecretKey ) - await t.click( - myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton - ) - await t - .expect( - autoDiscoverREDatabases.title.withExactText( - 'Redis Cloud Subscriptions' - ).exists - ) - .ok('Subscriptions list not displayed', { timeout: 120000 }) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await expect( + autoDiscoverREDatabases.title.getByText('Redis Cloud Subscriptions', { exact: true }) + ).toBeVisible({ timeout: 120000 }) // Select subscriptions - await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox) - await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton) + await myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox.click() + await myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton.click() // Select databases for adding const databaseName = await autoDiscoverREDatabases.getDatabaseName() - await t.click(autoDiscoverREDatabases.databaseCheckbox) - await t.click(autoDiscoverREDatabases.addSelectedDatabases) - // Wait for database to be exist in the redis databases list - await t - .expect( - autoDiscoverREDatabases.title.withExactText( - 'Redis Enterprise Databases Added' - ).exists - ) - .ok('Added databases list not displayed', { timeout: 20000 }) - await t.click(autoDiscoverREDatabases.viewDatabasesButton) + await autoDiscoverREDatabases.databaseCheckbox.click() + await autoDiscoverREDatabases.addSelectedDatabases.click() + // Wait for database to exist in the redis databases list + await expect( + autoDiscoverREDatabases.title.getByText('Redis Enterprise Databases Added', { exact: true }) + ).toBeVisible({ timeout: 20000 }) + await autoDiscoverREDatabases.viewDatabasesButton.click() // uncomment when fixed db will be added to cloud subscription - // await t.expect(myRedisDatabasePage.dbNameList.withExactText(databaseName).exists).ok('The database not displayed', { timeout: 10000 }); + // await expect(myRedisDatabasePage.dbNameList.getByText(databaseName, { exact: true })).toBeVisible({ timeout: 10000 }); return databaseName } @@ -189,9 +613,7 @@ export class DatabaseHelper { await this.acceptLicenseTerms() await this.addNewStandaloneDatabase(databaseParameters) // Connect to DB - await myRedisDatabasePage.clickOnDBByName( - databaseParameters.databaseName! - ) + await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) } /** @@ -203,15 +625,11 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { await this.acceptLicenseTerms() - await databaseAPIRequests.addNewStandaloneDatabaseApi( - databaseParameters - ) + await databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters) // Reload Page to see the new added database through api await myRedisDatabasePage.reloadPage() // Connect to DB - await myRedisDatabasePage.clickOnDBByName( - databaseParameters.databaseName! - ) + await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) } /** @@ -225,9 +643,7 @@ export class DatabaseHelper { await this.acceptLicenseTerms() await this.addOSSClusterDatabase(databaseParameters) // Connect to DB - await myRedisDatabasePage.clickOnDBByName( - databaseParameters.ossClusterDatabaseName! - ) + await myRedisDatabasePage.clickOnDBByName(databaseParameters.ossClusterDatabaseName!) } /** @@ -238,15 +654,11 @@ export class DatabaseHelper { databaseParameters: SentinelParameters ): Promise { await this.acceptLicenseTerms() - await databaseAPIRequests.discoverSentinelDatabaseApi( - databaseParameters - ) + await databaseAPIRequests.discoverSentinelDatabaseApi(databaseParameters) // Reload Page to see the database added through api await myRedisDatabasePage.reloadPage() // Connect to DB - await myRedisDatabasePage.clickOnDBByName( - databaseParameters.masters![1].alias ?? '' - ) + await myRedisDatabasePage.clickOnDBByName(databaseParameters.masters![1].alias ?? '') } /** @@ -259,9 +671,7 @@ export class DatabaseHelper { await this.acceptLicenseTerms() await this.addNewREClusterDatabase(databaseParameters) // Connect to DB - await myRedisDatabasePage.clickOnDBByName( - databaseParameters.databaseName ?? '' - ) + await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') } /** @@ -272,40 +682,23 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { const searchTimeout = 60 * 1000 // 60 sec to wait database appearing - const dbSelector = myRedisDatabasePage.dbNameList.withExactText( - databaseParameters.databaseName ?? '' - ) + const dbSelector = myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) const startTime = Date.now() await this.acceptLicenseTerms() - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( - databaseParameters - ) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase(databaseParameters) // Click for saving - await t.click( - myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton - ) - await t.wait(3000) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await myRedisDatabasePage.page.waitForTimeout(3000) // Reload page until db appears - do { + while (!(await dbSelector.isVisible()) && Date.now() - startTime < searchTimeout) { await myRedisDatabasePage.reloadPage() - } while ( - !(await dbSelector.exists) && - Date.now() - startTime < searchTimeout - ) - await t - .expect( - myRedisDatabasePage.dbNameList.withExactText( - databaseParameters.databaseName ?? '' - ).exists - ) - .ok('The database not displayed', { timeout: 5000 }) - await myRedisDatabasePage.clickOnDBByName( - databaseParameters.databaseName ?? '' - ) - await t - .expect(browserPage.keysSummary.exists) - .ok('Key list not loaded', { timeout: 15000 }) + } + await expect( + myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + ).toBeVisible({ timeout: 5000 }) + await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') + await expect(browserPage.keysSummary).toBeVisible({ timeout: 15000 }) } /** @@ -316,47 +709,34 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { const searchTimeout = 60 * 1000 // 60 sec to wait database appearing - const dbSelector = myRedisDatabasePage.dbNameList.withExactText( - databaseParameters.databaseName ?? '' - ) + const dbSelector = myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) const startTime = Date.now() - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( - databaseParameters - ) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase(databaseParameters) // Click for saving - await t.click( - myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton - ) - await t.wait(3000) + await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await myRedisDatabasePage.page.waitForTimeout(3000) // Reload page until db appears - do { + while (!(await dbSelector.isVisible()) && Date.now() - startTime < searchTimeout) { await myRedisDatabasePage.reloadPage() - } while ( - !(await dbSelector.exists) && - Date.now() - startTime < searchTimeout - ) - await t - .expect( - myRedisDatabasePage.dbNameList.withExactText( - databaseParameters.databaseName ?? '' - ).exists - ) - .ok('The database not displayed', { timeout: 5000 }) + } + await expect( + myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + ).toBeVisible({ timeout: 5000 }) } // Accept License terms async acceptLicenseTerms(): Promise { - await t.maximizeWindow() + // await myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) await userAgreementDialog.acceptLicenseTerms() await updateControlNumber(48.2) // Open default databases list tab if RDI opened - if (await rdiInstancesListPage.addRdiInstanceButton.exists) { + if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) } // TODO delete after releasing chatbot - if (await myRedisDatabasePage.AddRedisDatabaseDialog.aiChatMessage.exists) { - await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.aiCloseMessage) + if (await myRedisDatabasePage.AddRedisDatabaseDialog.aiChatMessage.isVisible()) { + await myRedisDatabasePage.AddRedisDatabaseDialog.aiCloseMessage.click() } } @@ -364,11 +744,8 @@ export class DatabaseHelper { async acceptLicenseAndConnectToRedisStack(): Promise { await this.acceptLicenseTerms() // Connect to DB - await t - .click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) - .click( - myRedisDatabasePage.AddRedisDatabaseDialog.connectToRedisStackButton - ) + await myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() + await myRedisDatabasePage.AddRedisDatabaseDialog.connectToRedisStackButton.click() } /** @@ -376,9 +753,9 @@ export class DatabaseHelper { * @param databaseName The database name */ async deleteDatabase(databaseName: string): Promise { - await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) + await myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() if ( - await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists + await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.isVisible() ) { await this.deleteDatabaseByNameApi(databaseName) } @@ -389,9 +766,9 @@ export class DatabaseHelper { * @param databaseName The database name */ async deleteCustomDatabase(databaseName: string): Promise { - await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) + await myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() if ( - await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists + await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.isVisible() ) { await myRedisDatabasePage.deleteDatabaseByName(databaseName) } @@ -406,11 +783,10 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { if ( - await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists + await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.isVisible() ) { await this.acceptLicenseTermsAndAddDatabase(databaseParameters) - } - else { + } else { await this.acceptLicenseAndConnectToRedisStack() } } @@ -420,17 +796,13 @@ export class DatabaseHelper { * @param databaseName The name of the database */ async clickOnEditDatabaseByName(databaseName: string): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName( - databaseName - ) - const databaseEditBtn = Selector( + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseEditBtn = myRedisDatabasePage.page.locator( `[data-testid=edit-instance-${databaseId}]` ) - await t - .expect(databaseEditBtn.exists) - .ok(`"${databaseName}" database not displayed`) - await t.click(databaseEditBtn) + await expect(databaseEditBtn).toBeVisible() + await databaseEditBtn.click() } /** @@ -438,17 +810,13 @@ export class DatabaseHelper { * @param databaseName The name of the database */ async deleteDatabaseByNameApi(databaseName: string): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName( - databaseName - ) - const databaseDeleteBtn = Selector( + const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseDeleteBtn = myRedisDatabasePage.page.locator( `[data-testid=delete-instance-${databaseId}-icon]` ) - await t - .expect(databaseDeleteBtn.exists) - .ok(`"${databaseName}" database not displayed`) - await t.click(databaseDeleteBtn) - await t.click(myRedisDatabasePage.confirmDeleteButton) + await expect(databaseDeleteBtn).toBeVisible() + await databaseDeleteBtn.click() + await myRedisDatabasePage.confirmDeleteButton.click() } } diff --git a/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts b/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts new file mode 100755 index 0000000000..30a6887db9 --- /dev/null +++ b/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts @@ -0,0 +1,30 @@ +import { Page, Locator } from '@playwright/test'; +import { BasePage } from './base-page'; + +export class AutoDiscoverREDatabases extends BasePage { + readonly page: Page; + // BUTTONS + readonly addSelectedDatabases: Locator; + readonly databaseCheckbox: Locator; + readonly search: Locator; + readonly viewDatabasesButton: Locator; + // TEXT INPUTS + readonly title: Locator; + readonly databaseName: Locator; + + constructor(page: Page) { + super(page) + this.page = page + this.addSelectedDatabases = page.getByTestId('btn-add-databases'); + this.databaseCheckbox = page.locator('[data-test-subj^="checkboxSelectRow"]'); + this.search = page.getByTestId('search'); + this.viewDatabasesButton = page.getByTestId('btn-view-databases'); + this.title = page.getByTestId('title'); + this.databaseName = page.locator('[data-testid^="db_name_"]'); + } + + // Get databases name + async getDatabaseName(): Promise { + return this.databaseName.textContent(); + } +} diff --git a/tests/playwright/pageObjects/base-overview-page.ts b/tests/playwright/pageObjects/base-overview-page.ts index 2ad48c9194..e0cffebc34 100644 --- a/tests/playwright/pageObjects/base-overview-page.ts +++ b/tests/playwright/pageObjects/base-overview-page.ts @@ -1,6 +1,6 @@ import { expect, Locator, Page } from '@playwright/test' import { Toast } from './components/common/toast' -import BasePage from './base-page' +import { BasePage } from './base-page' import { RedisOverviewPage } from '../helpers/constants' import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { DatabasesForImport } from '../types' diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index cb4eca7666..9c06fcab6a 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -1,6 +1,6 @@ import {Locator, Page, expect} from '@playwright/test' -export default class BasePage { +export class BasePage { protected page: Page constructor(page: Page) { @@ -56,4 +56,11 @@ export default class BasePage { const count = await locator.count() return count > 0 } + + async waitForLocatorVisible(locator: Locator, timeout = 6000) { + await expect(locator).toBeVisible({ timeout }) + } + async waitForLocatorNotVisible(locator: Locator, timeout = 6000) { + await expect(locator).not.toBeVisible({ timeout }) + } } diff --git a/tests/playwright/pageObjects/browser-page.ts b/tests/playwright/pageObjects/browser-page.ts index e1cae68cb6..921365beae 100755 --- a/tests/playwright/pageObjects/browser-page.ts +++ b/tests/playwright/pageObjects/browser-page.ts @@ -6,7 +6,7 @@ import {Toast } from './components/common/toast' // import { BulkActions, TreeView } from './components/browser' // import { AddNewKeyParameters , HashKeyParameters ,StreamKeyParameters, // SetKeyParameters , SortedSetKeyParameters ,ListKeyParameters, StringKeyParameters, AddKeyArguments, KeyData} from '../types' -import BasePage from './base-page' +import { BasePage } from './base-page' export class BrowserPage extends BasePage { // private readonly bulkActions: BulkActions diff --git a/tests/playwright/pageObjects/components/common/toast.ts b/tests/playwright/pageObjects/components/common/toast.ts index 6dd3640891..65bdfc313c 100644 --- a/tests/playwright/pageObjects/components/common/toast.ts +++ b/tests/playwright/pageObjects/components/common/toast.ts @@ -1,5 +1,5 @@ import {Locator, Page} from '@playwright/test' -import BasePage from '../../base-page' +import {BasePage} from '../../base-page' import {ToastSelectors} from '../../../selectors' export class Toast extends BasePage{ diff --git a/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts index d38060c7c8..97173299d2 100644 --- a/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts +++ b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts @@ -1,6 +1,7 @@ import { Locator, Page } from '@playwright/test' +import {BasePage} from "../base-page"; -export class RedisCloudSigninPanel { +export class RedisCloudSigninPanel extends BasePage { private readonly ssoOauthButton: Locator private readonly ssoEmailInput: Locator private readonly submitBtn: Locator @@ -10,6 +11,7 @@ export class RedisCloudSigninPanel { private readonly ssoOauth: Locator constructor(page: Page) { + super(page) this.ssoOauthButton = page.getByTestId('sso-oauth') this.ssoEmailInput = page.getByTestId('sso-email') this.submitBtn = page.getByTestId('btn-submit') diff --git a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts index 692a3d9771..98623e6c37 100755 --- a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts @@ -1,6 +1,7 @@ import { Page, Locator } from '@playwright/test' +import {BasePage} from "../base-page"; -export class AddRdiInstanceDialog { +export class AddRdiInstanceDialog extends BasePage{ readonly page: Page // INPUTS @@ -27,6 +28,7 @@ export class AddRdiInstanceDialog { readonly passwordInputInfoIcon: Locator constructor(page: Page) { + super(page) this.page = page this.rdiAliasInput = page.getByTestId('connection-form-name-input') this.urlInput = page.getByTestId('connection-form-url-input') diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts index 17b775b915..75f85c50c7 100644 --- a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts @@ -2,9 +2,10 @@ import { expect, Locator, Page } from '@playwright/test' import { TlsCertificates } from '../../helpers/constants' import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel' import {SentinelParameters, AddNewDatabaseParameters, SSHParameters } from '../../types' +import {BasePage} from "../base-page"; -export class AddRedisDatabaseDialog { +export class AddRedisDatabaseDialog extends BasePage{ private readonly page: Page private readonly redisCloudSigninPanel: RedisCloudSigninPanel @@ -68,6 +69,8 @@ export class AddRedisDatabaseDialog { } constructor(page: Page) { + + super(page) this.page = page this.redisCloudSigninPanel = new RedisCloudSigninPanel(page) diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index 235c1ad16c..097893a7ce 100644 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -1,5 +1,5 @@ import { expect, Locator, Page } from '@playwright/test' -import BasePage from '../base-page' +import {BasePage} from '../base-page' import { UserAgreementSelectors } from '../../selectors' export class UserAgreementDialog extends BasePage { diff --git a/tests/playwright/pageObjects/index.ts b/tests/playwright/pageObjects/index.ts new file mode 100644 index 0000000000..1c31ca0262 --- /dev/null +++ b/tests/playwright/pageObjects/index.ts @@ -0,0 +1,9 @@ +export * from './components/common/toast' +export * from './components/redis-cloud-sign-in-panel' +export * from './dialogs/add-rdi-instance-dialog' +export * from './dialogs/add-redis-database-dialog' +export * from './dialogs/user-agreement-dialog' +export * from './base-overview-page' +export * from './browser-page' +export * from './my-redis-databases-page' +export * from './rdi-instances-list-page' From 9de6c03740e78ee7ec12a0a4e0867f7f9b352803 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 16:18:21 +0200 Subject: [PATCH 039/128] wip PO --- tests/playwright/fixtures/electron.ts | 2 +- tests/playwright/helpers/database.ts | 295 +++++----- .../pageObjects/base-overview-page.ts | 4 +- .../dialogs/add-rdi-instance-dialog.ts | 2 +- .../dialogs/add-redis-database-dialog.ts | 30 +- tests/playwright/pageObjects/index.ts | 1 + .../pageObjects/my-redis-databases-page.ts | 554 +++++++++--------- .../playwright/tests/example.electron.spec.ts | 14 +- 8 files changed, 471 insertions(+), 431 deletions(-) diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts index 069e47036f..b9ebde6880 100644 --- a/tests/playwright/fixtures/electron.ts +++ b/tests/playwright/fixtures/electron.ts @@ -49,7 +49,7 @@ export const test = base.extend { diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 0e4496458c..9d0d3ec34e 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -9,14 +9,14 @@ // } from '../pageObjects/dialogs/add-redis-database-dialog' // import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' // import { -// MyRedisDatabasePage, +// this.myRedisDatabasePage, // BrowserPage, // AutoDiscoverREDatabases // } from '../pageObjects' // import { UserAgreementDialog } from '../pageObjects/dialogs' // import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' // -// const myRedisDatabasePage = new MyRedisDatabasePage() +// const this.myRedisDatabasePage = new this.myRedisDatabasePage() // const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() // const autoDiscoverREDatabases = new AutoDiscoverREDatabases() // const browserPage = new BrowserPage() @@ -33,21 +33,21 @@ // databaseParameters: AddNewDatabaseParameters // ): Promise { // // Fill the add database form -// await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( // databaseParameters // ) // // Click for saving // await t -// .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) +// .click(this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton) // // Wait for database to be exist // .expect( -// myRedisDatabasePage.dbNameList.withExactText( +// this.myRedisDatabasePage.dbNameList.withExactText( // databaseParameters.databaseName ?? '' // ).exists // ) // .ok('The database not displayed', { timeout: 10000 }) // // Close message -// .click(myRedisDatabasePage.Toast.toastCloseButton) +// .click(this.myRedisDatabasePage.Toast.toastCloseButton) // } // // /** @@ -58,13 +58,13 @@ // databaseParameters: SentinelParameters // ): Promise { // // Fill sentinel parameters to auto-discover Master Groups -// await myRedisDatabasePage.AddRedisDatabaseDialog.discoverSentinelDatabases( +// await this.myRedisDatabasePage.addRedisDatabaseDialog.discoverSentinelDatabases( // databaseParameters // ) // // Click for autodiscover // await t // .click( -// myRedisDatabasePage.AddRedisDatabaseDialog +// this.myRedisDatabasePage.AddRedisDatabaseDialog // .addRedisDatabaseButton // ) // .expect(discoverMasterGroupsPage.addPrimaryGroupButton.exists) @@ -84,12 +84,12 @@ // databaseParameters: AddNewDatabaseParameters // ): Promise { // // Fill the add database form -// await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverREClusterDatabase( +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverREClusterDatabase( // databaseParameters // ) // // Click on submit button // await t -// .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) +// .click(this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton) // // Wait for database to be exist in the list of Autodiscover databases and select it // .expect( // autoDiscoverREDatabases.databaseName.withExactText( @@ -115,18 +115,18 @@ // databaseParameters: OSSClusterParameters // ): Promise { // // Enter required parameters for OSS Cluster -// await myRedisDatabasePage.AddRedisDatabaseDialog.addOssClusterDatabase( +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addOssClusterDatabase( // databaseParameters // ) // // Click for saving // await t -// .click(myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton) +// .click(this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton) // // Check for info message that DB was added -// .expect(myRedisDatabasePage.Toast.toastHeader.exists) +// .expect(this.myRedisDatabasePage.Toast.toastHeader.exists) // .ok('Info message not exists', { timeout: 10000 }) // // Wait for database to be exist // .expect( -// myRedisDatabasePage.dbNameList.withExactText( +// this.myRedisDatabasePage.dbNameList.withExactText( // databaseParameters.ossClusterDatabaseName // ).exists // ) @@ -143,12 +143,12 @@ // cloudAPISecretKey: string // ): Promise { // // Fill the add database form and Submit -// await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverRECloudDatabase( +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverRECloudDatabase( // cloudAPIAccessKey, // cloudAPISecretKey // ) // await t.click( -// myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton +// this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton // ) // await t // .expect( @@ -158,8 +158,8 @@ // ) // .ok('Subscriptions list not displayed', { timeout: 120000 }) // // Select subscriptions -// await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox) -// await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton) +// await t.click(this.myRedisDatabasePage.addRedisDatabaseDialog.selectAllCheckbox) +// await t.click(this.myRedisDatabasePage.addRedisDatabaseDialog.showDatabasesButton) // // Select databases for adding // const databaseName = await autoDiscoverREDatabases.getDatabaseName() // await t.click(autoDiscoverREDatabases.databaseCheckbox) @@ -174,7 +174,7 @@ // .ok('Added databases list not displayed', { timeout: 20000 }) // await t.click(autoDiscoverREDatabases.viewDatabasesButton) // // uncomment when fixed db will be added to cloud subscription -// // await t.expect(myRedisDatabasePage.dbNameList.withExactText(databaseName).exists).ok('The database not displayed', { timeout: 10000 }); +// // await t.expect(this.myRedisDatabasePage.dbNameList.withExactText(databaseName).exists).ok('The database not displayed', { timeout: 10000 }); // return databaseName // } // @@ -189,7 +189,7 @@ // await this.acceptLicenseTerms() // await this.addNewStandaloneDatabase(databaseParameters) // // Connect to DB -// await myRedisDatabasePage.clickOnDBByName( +// await this.myRedisDatabasePage.clickOnDBByName( // databaseParameters.databaseName! // ) // } @@ -207,9 +207,9 @@ // databaseParameters // ) // // Reload Page to see the new added database through api -// await myRedisDatabasePage.reloadPage() +// await this.myRedisDatabasePage.reloadPage() // // Connect to DB -// await myRedisDatabasePage.clickOnDBByName( +// await this.myRedisDatabasePage.clickOnDBByName( // databaseParameters.databaseName! // ) // } @@ -225,7 +225,7 @@ // await this.acceptLicenseTerms() // await this.addOSSClusterDatabase(databaseParameters) // // Connect to DB -// await myRedisDatabasePage.clickOnDBByName( +// await this.myRedisDatabasePage.clickOnDBByName( // databaseParameters.ossClusterDatabaseName! // ) // } @@ -242,9 +242,9 @@ // databaseParameters // ) // // Reload Page to see the database added through api -// await myRedisDatabasePage.reloadPage() +// await this.myRedisDatabasePage.reloadPage() // // Connect to DB -// await myRedisDatabasePage.clickOnDBByName( +// await this.myRedisDatabasePage.clickOnDBByName( // databaseParameters.masters![1].alias ?? '' // ) // } @@ -259,7 +259,7 @@ // await this.acceptLicenseTerms() // await this.addNewREClusterDatabase(databaseParameters) // // Connect to DB -// await myRedisDatabasePage.clickOnDBByName( +// await this.myRedisDatabasePage.clickOnDBByName( // databaseParameters.databaseName ?? '' // ) // } @@ -272,35 +272,35 @@ // databaseParameters: AddNewDatabaseParameters // ): Promise { // const searchTimeout = 60 * 1000 // 60 sec to wait database appearing -// const dbSelector = myRedisDatabasePage.dbNameList.withExactText( +// const dbSelector = this.myRedisDatabasePage.dbNameList.withExactText( // databaseParameters.databaseName ?? '' // ) // const startTime = Date.now() // // await this.acceptLicenseTerms() -// await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( // databaseParameters // ) // // Click for saving // await t.click( -// myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton +// this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton // ) // await t.wait(3000) // // Reload page until db appears // do { -// await myRedisDatabasePage.reloadPage() +// await this.myRedisDatabasePage.reloadPage() // } while ( // !(await dbSelector.exists) && // Date.now() - startTime < searchTimeout // ) // await t // .expect( -// myRedisDatabasePage.dbNameList.withExactText( +// this.myRedisDatabasePage.dbNameList.withExactText( // databaseParameters.databaseName ?? '' // ).exists // ) // .ok('The database not displayed', { timeout: 5000 }) -// await myRedisDatabasePage.clickOnDBByName( +// await this.myRedisDatabasePage.clickOnDBByName( // databaseParameters.databaseName ?? '' // ) // await t @@ -316,29 +316,29 @@ // databaseParameters: AddNewDatabaseParameters // ): Promise { // const searchTimeout = 60 * 1000 // 60 sec to wait database appearing -// const dbSelector = myRedisDatabasePage.dbNameList.withExactText( +// const dbSelector = this.myRedisDatabasePage.dbNameList.withExactText( // databaseParameters.databaseName ?? '' // ) // const startTime = Date.now() // -// await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( // databaseParameters // ) // // Click for saving // await t.click( -// myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton +// this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton // ) // await t.wait(3000) // // Reload page until db appears // do { -// await myRedisDatabasePage.reloadPage() +// await this.myRedisDatabasePage.reloadPage() // } while ( // !(await dbSelector.exists) && // Date.now() - startTime < searchTimeout // ) // await t // .expect( -// myRedisDatabasePage.dbNameList.withExactText( +// this.myRedisDatabasePage.dbNameList.withExactText( // databaseParameters.databaseName ?? '' // ).exists // ) @@ -352,11 +352,11 @@ // await updateControlNumber(48.2) // // Open default databases list tab if RDI opened // if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { -// await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) +// await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) // } // // TODO delete after releasing chatbot -// if (await myRedisDatabasePage.AddRedisDatabaseDialog.aiChatMessage.exists) { -// await t.click(myRedisDatabasePage.AddRedisDatabaseDialog.aiCloseMessage) +// if (await this.myRedisDatabasePage.addRedisDatabaseDialog.aiChatMessage.exists) { +// await t.click(this.myRedisDatabasePage.addRedisDatabaseDialog.aiCloseMessage) // } // } // @@ -365,9 +365,9 @@ // await this.acceptLicenseTerms() // // Connect to DB // await t -// .click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) +// .click(this.myRedisDatabasePage.NavigationPanel.myRedisDBButton) // .click( -// myRedisDatabasePage.AddRedisDatabaseDialog.connectToRedisStackButton +// this.myRedisDatabasePage.addRedisDatabaseDialog.connectToRedisStackButton // ) // } // @@ -376,9 +376,9 @@ // * @param databaseName The database name // */ // async deleteDatabase(databaseName: string): Promise { -// await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) +// await t.click(this.myRedisDatabasePage.NavigationPanel.myRedisDBButton) // if ( -// await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.exists // ) { // await this.deleteDatabaseByNameApi(databaseName) // } @@ -389,11 +389,11 @@ // * @param databaseName The database name // */ // async deleteCustomDatabase(databaseName: string): Promise { -// await t.click(myRedisDatabasePage.NavigationPanel.myRedisDBButton) +// await t.click(this.myRedisDatabasePage.NavigationPanel.myRedisDBButton) // if ( -// await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.exists // ) { -// await myRedisDatabasePage.deleteDatabaseByName(databaseName) +// await this.myRedisDatabasePage.deleteDatabaseByName(databaseName) // } // } // @@ -406,7 +406,7 @@ // databaseParameters: AddNewDatabaseParameters // ): Promise { // if ( -// await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.exists +// await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.exists // ) { // await this.acceptLicenseTermsAndAddDatabase(databaseParameters) // } @@ -449,10 +449,10 @@ // .expect(databaseDeleteBtn.exists) // .ok(`"${databaseName}" database not displayed`) // await t.click(databaseDeleteBtn) -// await t.click(myRedisDatabasePage.confirmDeleteButton) +// await t.click(this.myRedisDatabasePage.confirmDeleteButton) // } // } -import { expect } from '@playwright/test' +import {expect, Page} from '@playwright/test' import { DatabaseAPIRequests } from './api/api-databases' import { RedisOverviewPage } from './constants' import { updateControlNumber } from './electron/insights' @@ -463,23 +463,37 @@ import { } from '../types' import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialog' import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' -import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' +// import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' import { MyRedisDatabasePage, BrowserPage, - AutoDiscoverREDatabases + AutoDiscoverREDatabases, + AddRedisDatabaseDialog } from '../pageObjects' -const myRedisDatabasePage = new MyRedisDatabasePage() -const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() -const autoDiscoverREDatabases = new AutoDiscoverREDatabases() -const browserPage = new BrowserPage() -const userAgreementDialog = new UserAgreementDialog() -const databaseAPIRequests = new DatabaseAPIRequests() -const rdiInstancesListPage = new RdiInstancesListPage() - export class DatabaseHelper { + private myRedisDatabasePage: MyRedisDatabasePage + private addRedisDataBaseDialog: AddRedisDatabaseDialog + +// const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() + private autoDiscoverREDatabases: AutoDiscoverREDatabases + private browserPage: BrowserPage + private userAgreementDialog: UserAgreementDialog + private databaseAPIRequests: DatabaseAPIRequests + private rdiInstancesListPage: RdiInstancesListPage + + constructor(page: Page, apiUrl: string) { + this.addRedisDataBaseDialog = new AddRedisDatabaseDialog(page) + this.autoDiscoverREDatabases = new AutoDiscoverREDatabases(page) + this.browserPage = new BrowserPage(page) + this.userAgreementDialog = new UserAgreementDialog(page) + this.rdiInstancesListPage = new RdiInstancesListPage(page) + this.myRedisDatabasePage = new MyRedisDatabasePage(page, apiUrl) + this.databaseAPIRequests = new DatabaseAPIRequests(apiUrl) + + + } /** * Add a new database manually using host and port * @param databaseParameters The database parameters @@ -488,39 +502,40 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { // Fill the add database form - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase( + + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( databaseParameters ) // Click for saving - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() // Wait for database to exist await expect( - myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) ).toBeVisible({ timeout: 10000 }) // Close message - await myRedisDatabasePage.Toast.toastCloseButton.click() + await this.myRedisDatabasePage.toast.toastCloseButton.click() } - /** - * Add a new database via autodiscover using Sentinel option - * @param databaseParameters The Sentinel parameters: host, port and sentinel password - */ - async discoverSentinelDatabase( - databaseParameters: SentinelParameters - ): Promise { - // Fill sentinel parameters to auto-discover Master Groups - await myRedisDatabasePage.AddRedisDatabaseDialog.discoverSentinelDatabases( - databaseParameters - ) - // Click for autodiscover - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() - await expect(discoverMasterGroupsPage.addPrimaryGroupButton).toBeVisible({ - timeout: 10000 - }) - // Select Master Groups and Add to Redis Insight - await discoverMasterGroupsPage.addMasterGroups() - await autoDiscoverREDatabases.viewDatabasesButton.click() - } + // /** + // * Add a new database via autodiscover using Sentinel option + // * @param databaseParameters The Sentinel parameters: host, port and sentinel password + // */ + // async discoverSentinelDatabase( + // databaseParameters: SentinelParameters + // ): Promise { + // // Fill sentinel parameters to auto-discover Master Groups + // await this.myRedisDatabasePage.addRedisDatabaseDialog.discoverSentinelDatabases( + // databaseParameters + // ) + // // Click for autodiscover + // await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() + // await expect(discoverMasterGroupsPage.addPrimaryGroupButton).toBeVisible({ + // timeout: 10000 + // }) + // // Select Master Groups and Add to Redis Insight + // await discoverMasterGroupsPage.addMasterGroups() + // await autoDiscoverREDatabases.viewDatabasesButton.click() + // } /** * Add a new database from RE Cluster via auto-discover flow @@ -530,20 +545,20 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { // Fill the add database form - await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverREClusterDatabase( + await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverREClusterDatabase( databaseParameters ) // Click on submit button - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() // Wait for database to exist in the list of Autodiscover databases and select it await expect( - autoDiscoverREDatabases.databaseName.getByText(databaseParameters.databaseName ?? '', { exact: true }) + this.autoDiscoverREDatabases.databaseName.getByText(databaseParameters.databaseName ?? '', { exact: true }) ).toBeVisible({ timeout: 10000 }) - await autoDiscoverREDatabases.search.fill(databaseParameters.databaseName ?? '') - await autoDiscoverREDatabases.databaseCheckbox.click() + await this.autoDiscoverREDatabases.search.fill(databaseParameters.databaseName ?? '') + await this.autoDiscoverREDatabases.databaseCheckbox.click() // Click Add selected databases button - await autoDiscoverREDatabases.addSelectedDatabases.click() - await autoDiscoverREDatabases.viewDatabasesButton.click() + await this.autoDiscoverREDatabases.addSelectedDatabases.click() + await this.autoDiscoverREDatabases.viewDatabasesButton.click() } /** @@ -554,16 +569,16 @@ export class DatabaseHelper { databaseParameters: OSSClusterParameters ): Promise { // Enter required parameters for OSS Cluster - await myRedisDatabasePage.AddRedisDatabaseDialog.addOssClusterDatabase( + await this.myRedisDatabasePage.addRedisDatabaseDialog.addOssClusterDatabase( databaseParameters ) // Click for saving - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() // Check for info message that DB was added - await expect(myRedisDatabasePage.Toast.toastHeader).toBeVisible({ timeout: 10000 }) + await expect(this.myRedisDatabasePage.toast.toastHeader).toBeVisible({ timeout: 10000 }) // Wait for database to exist await expect( - myRedisDatabasePage.dbNameList.getByText(databaseParameters.ossClusterDatabaseName, { exact: true }) + this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.ossClusterDatabaseName, { exact: true }) ).toBeVisible({ timeout: 10000 }) } @@ -577,17 +592,17 @@ export class DatabaseHelper { cloudAPISecretKey: string ): Promise { // Fill the add database form and Submit - await myRedisDatabasePage.AddRedisDatabaseDialog.addAutodiscoverRECloudDatabase( + await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverRECloudDatabase( cloudAPIAccessKey, cloudAPISecretKey ) - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() await expect( autoDiscoverREDatabases.title.getByText('Redis Cloud Subscriptions', { exact: true }) ).toBeVisible({ timeout: 120000 }) // Select subscriptions - await myRedisDatabasePage.AddRedisDatabaseDialog.selectAllCheckbox.click() - await myRedisDatabasePage.AddRedisDatabaseDialog.showDatabasesButton.click() + await this.myRedisDatabasePage.addRedisDatabaseDialog.selectAllCheckbox.click() + await this.myRedisDatabasePage.addRedisDatabaseDialog.showDatabasesButton.click() // Select databases for adding const databaseName = await autoDiscoverREDatabases.getDatabaseName() await autoDiscoverREDatabases.databaseCheckbox.click() @@ -598,7 +613,7 @@ export class DatabaseHelper { ).toBeVisible({ timeout: 20000 }) await autoDiscoverREDatabases.viewDatabasesButton.click() // uncomment when fixed db will be added to cloud subscription - // await expect(myRedisDatabasePage.dbNameList.getByText(databaseName, { exact: true })).toBeVisible({ timeout: 10000 }); + // await expect(this.myRedisDatabasePage.dbNameList.getByText(databaseName, { exact: true })).toBeVisible({ timeout: 10000 }); return databaseName } @@ -613,7 +628,7 @@ export class DatabaseHelper { await this.acceptLicenseTerms() await this.addNewStandaloneDatabase(databaseParameters) // Connect to DB - await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) + await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) } /** @@ -622,14 +637,16 @@ export class DatabaseHelper { * @param databaseName The database name */ async acceptLicenseTermsAndAddDatabaseApi( - databaseParameters: AddNewDatabaseParameters + databaseParameters: AddNewDatabaseParameters, + page: Page, + apiUrl: string ): Promise { - await this.acceptLicenseTerms() + await this.acceptLicenseTerms(page,apiUrl) await databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters) // Reload Page to see the new added database through api - await myRedisDatabasePage.reloadPage() + await this.myRedisDatabasePage.reloadPage() // Connect to DB - await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) + await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) } /** @@ -643,7 +660,7 @@ export class DatabaseHelper { await this.acceptLicenseTerms() await this.addOSSClusterDatabase(databaseParameters) // Connect to DB - await myRedisDatabasePage.clickOnDBByName(databaseParameters.ossClusterDatabaseName!) + await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.ossClusterDatabaseName!) } /** @@ -656,9 +673,9 @@ export class DatabaseHelper { await this.acceptLicenseTerms() await databaseAPIRequests.discoverSentinelDatabaseApi(databaseParameters) // Reload Page to see the database added through api - await myRedisDatabasePage.reloadPage() + await this.myRedisDatabasePage.reloadPage() // Connect to DB - await myRedisDatabasePage.clickOnDBByName(databaseParameters.masters![1].alias ?? '') + await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.masters![1].alias ?? '') } /** @@ -671,7 +688,7 @@ export class DatabaseHelper { await this.acceptLicenseTerms() await this.addNewREClusterDatabase(databaseParameters) // Connect to DB - await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') + await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') } /** @@ -682,22 +699,22 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { const searchTimeout = 60 * 1000 // 60 sec to wait database appearing - const dbSelector = myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + const dbSelector = this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) const startTime = Date.now() await this.acceptLicenseTerms() - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase(databaseParameters) + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase(databaseParameters) // Click for saving - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() - await myRedisDatabasePage.page.waitForTimeout(3000) + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() + await this.myRedisDatabasePage.page.waitForTimeout(3000) // Reload page until db appears while (!(await dbSelector.isVisible()) && Date.now() - startTime < searchTimeout) { - await myRedisDatabasePage.reloadPage() + await this.myRedisDatabasePage.reloadPage() } await expect( - myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) ).toBeVisible({ timeout: 5000 }) - await myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') + await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') await expect(browserPage.keysSummary).toBeVisible({ timeout: 15000 }) } @@ -709,34 +726,34 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { const searchTimeout = 60 * 1000 // 60 sec to wait database appearing - const dbSelector = myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + const dbSelector = this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) const startTime = Date.now() - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDataBase(databaseParameters) + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase(databaseParameters) // Click for saving - await myRedisDatabasePage.AddRedisDatabaseDialog.addRedisDatabaseButton.click() - await myRedisDatabasePage.page.waitForTimeout(3000) + await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() + await this.myRedisDatabasePage.page.waitForTimeout(3000) // Reload page until db appears while (!(await dbSelector.isVisible()) && Date.now() - startTime < searchTimeout) { - await myRedisDatabasePage.reloadPage() + await this.myRedisDatabasePage.reloadPage() } await expect( - myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) + this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) ).toBeVisible({ timeout: 5000 }) } // Accept License terms - async acceptLicenseTerms(): Promise { - // await myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) + async acceptLicenseTerms(page: Page, apiUrl: string ): Promise { + // await this.myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) await userAgreementDialog.acceptLicenseTerms() - await updateControlNumber(48.2) + await updateControlNumber(48.2, page, apiUrl) // Open default databases list tab if RDI opened if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { - await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) + await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) } // TODO delete after releasing chatbot - if (await myRedisDatabasePage.AddRedisDatabaseDialog.aiChatMessage.isVisible()) { - await myRedisDatabasePage.AddRedisDatabaseDialog.aiCloseMessage.click() + if (await this.myRedisDatabasePage.addRedisDatabaseDialog.aiChatMessage.isVisible()) { + await this.myRedisDatabasePage.addRedisDatabaseDialog.aiCloseMessage.click() } } @@ -744,8 +761,8 @@ export class DatabaseHelper { async acceptLicenseAndConnectToRedisStack(): Promise { await this.acceptLicenseTerms() // Connect to DB - await myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() - await myRedisDatabasePage.AddRedisDatabaseDialog.connectToRedisStackButton.click() + await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() + await this.myRedisDatabasePage.addRedisDatabaseDialog.connectToRedisStackButton.click() } /** @@ -753,9 +770,9 @@ export class DatabaseHelper { * @param databaseName The database name */ async deleteDatabase(databaseName: string): Promise { - await myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() + await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() if ( - await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.isVisible() + await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() ) { await this.deleteDatabaseByNameApi(databaseName) } @@ -766,11 +783,11 @@ export class DatabaseHelper { * @param databaseName The database name */ async deleteCustomDatabase(databaseName: string): Promise { - await myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() + await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() if ( - await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.isVisible() + await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() ) { - await myRedisDatabasePage.deleteDatabaseByName(databaseName) + await this.myRedisDatabasePage.deleteDatabaseByName(databaseName) } } @@ -783,7 +800,7 @@ export class DatabaseHelper { databaseParameters: AddNewDatabaseParameters ): Promise { if ( - await myRedisDatabasePage.AddRedisDatabaseDialog.addDatabaseButton.isVisible() + await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() ) { await this.acceptLicenseTermsAndAddDatabase(databaseParameters) } else { @@ -797,7 +814,7 @@ export class DatabaseHelper { */ async clickOnEditDatabaseByName(databaseName: string): Promise { const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) - const databaseEditBtn = myRedisDatabasePage.page.locator( + const databaseEditBtn = this.myRedisDatabasePage.page.locator( `[data-testid=edit-instance-${databaseId}]` ) @@ -811,12 +828,12 @@ export class DatabaseHelper { */ async deleteDatabaseByNameApi(databaseName: string): Promise { const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) - const databaseDeleteBtn = myRedisDatabasePage.page.locator( + const databaseDeleteBtn = this.myRedisDatabasePage.page.locator( `[data-testid=delete-instance-${databaseId}-icon]` ) await expect(databaseDeleteBtn).toBeVisible() await databaseDeleteBtn.click() - await myRedisDatabasePage.confirmDeleteButton.click() + await this.myRedisDatabasePage.confirmDeleteButton.click() } } diff --git a/tests/playwright/pageObjects/base-overview-page.ts b/tests/playwright/pageObjects/base-overview-page.ts index e0cffebc34..94f36762a2 100644 --- a/tests/playwright/pageObjects/base-overview-page.ts +++ b/tests/playwright/pageObjects/base-overview-page.ts @@ -8,7 +8,7 @@ import { DatabasesForImport } from '../types' export class BaseOverviewPage extends BasePage { // Component instance used in methods - private readonly toast: Toast + toast: Toast // BUTTONS & ACTION SELECTORS private readonly deleteRowButton: Locator @@ -24,7 +24,7 @@ export class BaseOverviewPage extends BasePage { private readonly deleteButtonInPopover: Locator - private readonly dbNameList: Locator + public dbNameList: Locator private readonly tableRowContent: Locator diff --git a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts index 98623e6c37..2426f67194 100755 --- a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts @@ -1,5 +1,5 @@ import { Page, Locator } from '@playwright/test' -import {BasePage} from "../base-page"; +import {BasePage} from "../base-page" export class AddRdiInstanceDialog extends BasePage{ readonly page: Page diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts index 75f85c50c7..bc8172430d 100644 --- a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts @@ -2,48 +2,68 @@ import { expect, Locator, Page } from '@playwright/test' import { TlsCertificates } from '../../helpers/constants' import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel' import {SentinelParameters, AddNewDatabaseParameters, SSHParameters } from '../../types' -import {BasePage} from "../base-page"; +import {BasePage} from '../base-page' export class AddRedisDatabaseDialog extends BasePage{ private readonly page: Page + private readonly redisCloudSigninPanel: RedisCloudSigninPanel // BUTTONS private readonly addDatabaseButton: Locator - private readonly addRedisDatabaseButton: Locator + + readonly addRedisDatabaseButton: Locator + private readonly customSettingsButton: Locator + private readonly addAutoDiscoverDatabase: Locator + private readonly addCloudDatabaseButton: Locator + private readonly redisSoftwareButton: Locator + private readonly redisSentinelButton: Locator // TEXT INPUTS private readonly hostInput: Locator + private readonly portInput: Locator + private readonly databaseAliasInput: Locator + private readonly passwordInput: Locator + private readonly usernameInput: Locator + private readonly accessKeyInput: Locator + private readonly secretKeyInput: Locator + private readonly databaseIndexInput: Locator // TABS private readonly generalTab: Locator + private readonly securityTab: Locator + private readonly decompressionTab: Locator // DROPDOWNS private readonly caCertField: Locator + private readonly clientCertField: Locator + private readonly selectCompressor: Locator // CHECKBOXES private readonly databaseIndexCheckbox: Locator + private readonly useSSHCheckbox: Locator // RADIO BUTTONS private readonly sshPasswordRadioBtn: Locator + private readonly sshPrivateKeyRadioBtn: Locator // LABELS @@ -51,10 +71,15 @@ export class AddRedisDatabaseDialog extends BasePage{ // SSH TEXT INPUTS private readonly sshHostInput: Locator + private readonly sshPortInput: Locator + private readonly sshUsernameInput: Locator + private readonly sshPasswordInput: Locator + private readonly sshPrivateKeyInput: Locator + private readonly sshPassphraseInput: Locator // OTHER @@ -64,6 +89,7 @@ export class AddRedisDatabaseDialog extends BasePage{ private trashIconMsk(certificate: TlsCertificates): string { return `[data-testid^="delete-${certificate}-cert"]` } + private getDeleteCertificate(certificate: TlsCertificates): Locator { return this.page.locator(this.trashIconMsk(certificate)) } diff --git a/tests/playwright/pageObjects/index.ts b/tests/playwright/pageObjects/index.ts index 1c31ca0262..c005179146 100644 --- a/tests/playwright/pageObjects/index.ts +++ b/tests/playwright/pageObjects/index.ts @@ -7,3 +7,4 @@ export * from './base-overview-page' export * from './browser-page' export * from './my-redis-databases-page' export * from './rdi-instances-list-page' +export * from './auto-discover-redis-enterprise-databases' diff --git a/tests/playwright/pageObjects/my-redis-databases-page.ts b/tests/playwright/pageObjects/my-redis-databases-page.ts index 95eb6815ad..fa6568d452 100755 --- a/tests/playwright/pageObjects/my-redis-databases-page.ts +++ b/tests/playwright/pageObjects/my-redis-databases-page.ts @@ -5,7 +5,8 @@ import { Toast } from './components/common/toast' // import { NavigationPanel } from './components/navigation-panel' // import { NavigationHeader } from './components/navigation/navigation-header' // import { AuthorizationDialog } from './dialogs/authorization-dialog' -// import { AddRedisDatabaseDialog } from './dialogs' +import { AddRedisDatabaseDialog } from './dialogs/add-redis-database-dialog' +import {DatabaseAPIRequests} from '../helpers/api/api-databases'; // import { DatabaseAPIRequests } from '../helpers/api/api-database' export type DatabasesForImport = { @@ -31,217 +32,218 @@ export type DatabasesForImport = { export class MyRedisDatabasePage extends BaseOverviewPage { // // Component Instances // private readonly navigationPanel: NavigationPanel - // - // private readonly addRedisDatabaseDialog: AddRedisDatabaseDialog - // + + + readonly addRedisDatabaseDialog: AddRedisDatabaseDialog + // private readonly insightsPanel: InsightsPanel - // + // private readonly navigationHeader: NavigationHeader - // + // private readonly authorizationDialog: AuthorizationDialog - // - // // API instance - // private readonly databaseAPIRequests: DatabaseAPIRequests - // - // // CSS Selectors - // private readonly cssNumberOfDbs: Locator - // - // private readonly cssRedisStackIcon: Locator - // - // // BUTTONS - // private readonly deleteDatabaseButton: Locator - // - // private readonly confirmDeleteButton: Locator - // - // private readonly deleteButtonInPopover: Locator - // - // private readonly confirmDeleteAllDbButton: Locator - // - // private readonly editDatabaseButton: Locator - // - // private readonly popoverHeader: Locator - // - // private readonly submitChangesButton: Locator - // - // private readonly promoButton: Locator - // - // private readonly sortByDatabaseAlias: Locator - // - // private readonly sortByHostAndPort: Locator - // - // private readonly sortByConnectionType: Locator - // - // private readonly importDatabasesBtn: Locator - // - // private readonly retryImportBtn: Locator - // - // private readonly removeImportedFileBtn: Locator - // - // private readonly exportBtn: Locator - // - // private readonly exportSelectedDbsBtn: Locator - // - // private readonly userProfileBtn: Locator - // - // private readonly closeImportBtn: Locator - // - // // CHECKBOXES - // private readonly selectAllCheckbox: Locator - // - // private readonly exportPasswordsCheckbox: Locator - // - // private readonly starFreeDbCheckbox: Locator - // - // // ICONS - // private readonly moduleColumn: Locator - // - // private readonly moduleSearchIcon: Locator - // - // private readonly moduleGraphIcon: Locator - // - // private readonly moduleJSONIcon: Locator - // - // private readonly moduleTimeseriesIcon: Locator - // - // private readonly moduleBloomIcon: Locator - // - // private readonly moduleAIIcon: Locator - // - // private readonly moduleGearsIcon: Locator - // - // private readonly redisStackIcon: Locator - // - // private readonly tooltipRedisStackLogo: Locator - // - // private readonly iconNotUsedDatabase: Locator - // - // private readonly iconDeletedDatabase: Locator - // - // // TEXT INPUTS - // private readonly searchInput: Locator - // - // private readonly importDatabaseInput: Locator - // - // // TEXT ELEMENTS - // private readonly moduleTooltip: Locator - // - // private readonly moduleQuantifier: Locator + + // API instance + private readonly databaseAPIRequests: DatabaseAPIRequests + + // CSS Selectors + private readonly cssNumberOfDbs: Locator + + private readonly cssRedisStackIcon: Locator + + // BUTTONS + private readonly deleteDatabaseButton: Locator + + private readonly confirmDeleteButton: Locator + + private readonly deleteButtonInPopover: Locator + + private readonly confirmDeleteAllDbButton: Locator + + private readonly editDatabaseButton: Locator + + private readonly popoverHeader: Locator + + private readonly submitChangesButton: Locator + + private readonly promoButton: Locator + + private readonly sortByDatabaseAlias: Locator + + private readonly sortByHostAndPort: Locator + + private readonly sortByConnectionType: Locator + + private readonly importDatabasesBtn: Locator + + private readonly retryImportBtn: Locator + + private readonly removeImportedFileBtn: Locator + + private readonly exportBtn: Locator + + private readonly exportSelectedDbsBtn: Locator + + private readonly userProfileBtn: Locator + + private readonly closeImportBtn: Locator + + // CHECKBOXES + private readonly selectAllCheckbox: Locator + + private readonly exportPasswordsCheckbox: Locator + + private readonly starFreeDbCheckbox: Locator + + // ICONS + private readonly moduleColumn: Locator + + private readonly moduleSearchIcon: Locator + + private readonly moduleGraphIcon: Locator + + private readonly moduleJSONIcon: Locator + + private readonly moduleTimeseriesIcon: Locator + + private readonly moduleBloomIcon: Locator + + private readonly moduleAIIcon: Locator + + private readonly moduleGearsIcon: Locator + + private readonly redisStackIcon: Locator + + private readonly tooltipRedisStackLogo: Locator + + private readonly iconNotUsedDatabase: Locator + + private readonly iconDeletedDatabase: Locator + + // TEXT INPUTS + private readonly searchInput: Locator + + private readonly importDatabaseInput: Locator + + // TEXT ELEMENTS + private readonly moduleTooltip: Locator + + private readonly moduleQuantifier: Locator // private readonly dbNameList: Locator - // - // private readonly tableRowContent: Locator - // - // private readonly hostPort: Locator - // - // private readonly failedImportMessage: Locator - // - // private readonly importResult: Locator - // - // private readonly userProfileAccountInfo: Locator - // - // private readonly portCloudDb: Locator - // - // // DIALOG - // private readonly successResultsAccordion: Locator - // - // private readonly partialResultsAccordion: Locator - // - // private readonly failedResultsAccordion: Locator - // - // private readonly notificationUnusedDbMessage: Locator - // - // // CONTAINERS - // private readonly databaseContainer: Locator - // - // private readonly connectionTypeTitle: Locator - // - // private readonly addDatabaseImport: Locator - // - // // Assumed additional selector needed in deleteDatabaseByName method - // private readonly deleteRowButton: Locator - constructor(page: Page) { + private readonly tableRowContent: Locator + + private readonly hostPort: Locator + + private readonly failedImportMessage: Locator + + private readonly importResult: Locator + + private readonly userProfileAccountInfo: Locator + + private readonly portCloudDb: Locator + + // DIALOG + private readonly successResultsAccordion: Locator + + private readonly partialResultsAccordion: Locator + + private readonly failedResultsAccordion: Locator + + private readonly notificationUnusedDbMessage: Locator + + // CONTAINERS + private readonly databaseContainer: Locator + + private readonly connectionTypeTitle: Locator + + private readonly addDatabaseImport: Locator + + // Assumed additional selector needed in deleteDatabaseByName method + private readonly deleteRowButton: Locator + + constructor(page: Page, apiUr: string) { super(page) - // this.databaseAPIRequests = new DatabaseAPIRequests() - // - // // Initialize component instances - // this.navigationPanel = new NavigationPanel(page) - // this.addRedisDatabaseDialog = new AddRedisDatabaseDialog(page) - // this.insightsPanel = new InsightsPanel(page) - // this.navigationHeader = new NavigationHeader(page) - // this.authorizationDialog = new AuthorizationDialog(page) - // - // // CSS Selectors - // this.cssNumberOfDbs = page.getByTestId('number-of-dbs') - // this.cssRedisStackIcon = page.getByTestId('redis-stack-icon') - // - // // BUTTONS - // this.deleteDatabaseButton = page.locator('[data-testid^="delete-instance-"]') - // this.confirmDeleteButton = page.locator('[data-testid^="delete-instance-"]').filter({ hasText: 'Remove' }) - // this.deleteButtonInPopover = page.locator('#deletePopover button') - // this.confirmDeleteAllDbButton = page.getByTestId('delete-selected-dbs') - // this.editDatabaseButton = page.locator('[data-testid^="edit-instance"]') - // this.popoverHeader = page.locator('#formModalHeader') - // this.submitChangesButton = page.getByTestId('btn-submit') - // this.promoButton = page.getByTestId('promo-btn') - // this.sortByDatabaseAlias = page.locator('span[title="Database Alias"]') - // this.sortByHostAndPort = page.locator('span[title="Host:Port"]') - // this.sortByConnectionType = page.locator('span[title="Connection Type"]') - // this.importDatabasesBtn = page.getByTestId('option-btn-import') - // this.retryImportBtn = page.getByTestId('btn-retry') - // this.removeImportedFileBtn = page.locator('[aria-label="Clear selected files"]') - // this.exportBtn = page.getByTestId('export-btn') - // this.exportSelectedDbsBtn = page.getByTestId('export-selected-dbs') - // this.userProfileBtn = page.getByTestId('user-profile-btn') - // this.closeImportBtn = page.getByTestId('btn-close') - // - // // CHECKBOXES - // this.selectAllCheckbox = page.locator('[data-test-subj="checkboxSelectAll"]') - // this.exportPasswordsCheckbox = page.locator('[data-testid="export-passwords"] ~ div') - // this.starFreeDbCheckbox = page.locator('[data-test-subj="checkboxSelectRow-create-free-cloud-db"]') - // - // // ICONS - // this.moduleColumn = page.locator('[data-test-subj="tableHeaderCell_modules_3"]') - // this.moduleSearchIcon = page.locator('[data-testid^="Redis Query Engine"]') - // this.moduleGraphIcon = page.locator('[data-testid^="Graph"]') - // this.moduleJSONIcon = page.locator('[data-testid^="JSON"]') - // this.moduleTimeseriesIcon = page.locator('[data-testid^="Time Series"]') - // this.moduleBloomIcon = page.locator('[data-testid^="Probabilistic"]') - // this.moduleAIIcon = page.locator('[data-testid^="AI"]') - // this.moduleGearsIcon = page.locator('[data-testid^="Gears"]') - // this.redisStackIcon = page.getByTestId('redis-stack-icon') - // this.tooltipRedisStackLogo = page.getByTestId('tooltip-redis-stack-icon') - // this.iconNotUsedDatabase = page.locator('[data-testid^="database-status-tryDatabase-"]') - // this.iconDeletedDatabase = page.locator('[data-testid^="database-status-checkIfDeleted-"]') - // - // // TEXT INPUTS - // this.searchInput = page.getByTestId('search-database-list') - // this.importDatabaseInput = page.getByTestId('import-file-modal-filepicker') - // - // // TEXT ELEMENTS - // this.moduleTooltip = page.locator('.euiToolTipPopover') - // this.moduleQuantifier = page.getByTestId('_module') + this.databaseAPIRequests = new DatabaseAPIRequests(apiUr) + + // Initialize component instances + // this.navigationPanel = new NavigationPanel(page) + this.addRedisDatabaseDialog = new AddRedisDatabaseDialog(page) + // this.insightsPanel = new InsightsPanel(page) + // this.navigationHeader = new NavigationHeader(page) + // this.authorizationDialog = new AuthorizationDialog(page) + + // CSS Selectors + this.cssNumberOfDbs = page.getByTestId('number-of-dbs') + this.cssRedisStackIcon = page.getByTestId('redis-stack-icon') + + // BUTTONS + this.deleteDatabaseButton = page.locator('[data-testid^="delete-instance-"]') + this.confirmDeleteButton = page.locator('[data-testid^="delete-instance-"]').filter({ hasText: 'Remove' }) + this.deleteButtonInPopover = page.locator('#deletePopover button') + this.confirmDeleteAllDbButton = page.getByTestId('delete-selected-dbs') + this.editDatabaseButton = page.locator('[data-testid^="edit-instance"]') + this.popoverHeader = page.locator('#formModalHeader') + this.submitChangesButton = page.getByTestId('btn-submit') + this.promoButton = page.getByTestId('promo-btn') + this.sortByDatabaseAlias = page.locator('span[title="Database Alias"]') + this.sortByHostAndPort = page.locator('span[title="Host:Port"]') + this.sortByConnectionType = page.locator('span[title="Connection Type"]') + this.importDatabasesBtn = page.getByTestId('option-btn-import') + this.retryImportBtn = page.getByTestId('btn-retry') + this.removeImportedFileBtn = page.locator('[aria-label="Clear selected files"]') + this.exportBtn = page.getByTestId('export-btn') + this.exportSelectedDbsBtn = page.getByTestId('export-selected-dbs') + this.userProfileBtn = page.getByTestId('user-profile-btn') + this.closeImportBtn = page.getByTestId('btn-close') + + // CHECKBOXES + this.selectAllCheckbox = page.locator('[data-test-subj="checkboxSelectAll"]') + this.exportPasswordsCheckbox = page.locator('[data-testid="export-passwords"] ~ div') + this.starFreeDbCheckbox = page.locator('[data-test-subj="checkboxSelectRow-create-free-cloud-db"]') + + // ICONS + this.moduleColumn = page.locator('[data-test-subj="tableHeaderCell_modules_3"]') + this.moduleSearchIcon = page.locator('[data-testid^="Redis Query Engine"]') + this.moduleGraphIcon = page.locator('[data-testid^="Graph"]') + this.moduleJSONIcon = page.locator('[data-testid^="JSON"]') + this.moduleTimeseriesIcon = page.locator('[data-testid^="Time Series"]') + this.moduleBloomIcon = page.locator('[data-testid^="Probabilistic"]') + this.moduleAIIcon = page.locator('[data-testid^="AI"]') + this.moduleGearsIcon = page.locator('[data-testid^="Gears"]') + this.redisStackIcon = page.getByTestId('redis-stack-icon') + this.tooltipRedisStackLogo = page.getByTestId('tooltip-redis-stack-icon') + this.iconNotUsedDatabase = page.locator('[data-testid^="database-status-tryDatabase-"]') + this.iconDeletedDatabase = page.locator('[data-testid^="database-status-checkIfDeleted-"]') + + // TEXT INPUTS + this.searchInput = page.getByTestId('search-database-list') + this.importDatabaseInput = page.getByTestId('import-file-modal-filepicker') + + // TEXT ELEMENTS + this.moduleTooltip = page.locator('.euiToolTipPopover') + this.moduleQuantifier = page.getByTestId('_module') this.dbNameList = page.locator('[data-testid^="instance-name"]') - // this.tableRowContent = page.locator('[data-test-subj="database-alias-column"]') - // this.hostPort = page.getByTestId('host-port') - // this.failedImportMessage = page.getByTestId('result-failed') - // this.importResult = page.locator('[data-testid^="table-result-"]') - // this.userProfileAccountInfo = page.locator('[data-testid^="profile-account-"]') - // this.portCloudDb = page.locator('[class*="column_host"]') - // - // // DIALOG - // this.successResultsAccordion = page.locator('[data-testid^="success-results-"]') - // this.partialResultsAccordion = page.locator('[data-testid^="partial-results-"]') - // this.failedResultsAccordion = page.locator('[data-testid^="failed-results-"]') - // this.notificationUnusedDbMessage = page.locator('[class^="_warningTooltipContent"]') - // - // // CONTAINERS - // this.databaseContainer = page.locator('.databaseContainer') - // this.connectionTypeTitle = page.locator('[data-test-subj="tableHeaderCell_connectionType_2"]') - // this.addDatabaseImport = page.getByTestId('add-db_import') - // - // // Additional property assumed for deleteDatabaseByName: - // this.deleteRowButton = page.locator('[data-testid^="delete-instance-"]') + this.tableRowContent = page.locator('[data-test-subj="database-alias-column"]') + this.hostPort = page.getByTestId('host-port') + this.failedImportMessage = page.getByTestId('result-failed') + this.importResult = page.locator('[data-testid^="table-result-"]') + this.userProfileAccountInfo = page.locator('[data-testid^="profile-account-"]') + this.portCloudDb = page.locator('[class*="column_host"]') + + // DIALOG + this.successResultsAccordion = page.locator('[data-testid^="success-results-"]') + this.partialResultsAccordion = page.locator('[data-testid^="partial-results-"]') + this.failedResultsAccordion = page.locator('[data-testid^="failed-results-"]') + this.notificationUnusedDbMessage = page.locator('[class^="_warningTooltipContent"]') + + // CONTAINERS + this.databaseContainer = page.locator('.databaseContainer') + this.connectionTypeTitle = page.locator('[data-test-subj="tableHeaderCell_connectionType_2"]') + this.addDatabaseImport = page.getByTestId('add-db_import') + + // Additional property assumed for deleteDatabaseByName: + this.deleteRowButton = page.locator('[data-testid^="delete-instance-"]') } async clickOnDBByName(dbName: string): Promise { @@ -270,76 +272,76 @@ export class MyRedisDatabasePage extends BaseOverviewPage { // await this.toast.toastCloseButton.click() // } // } - // - // async deleteDatabaseByName(dbName: string): Promise { - // const dbNames = this.tableRowContent - // const count = await dbNames.count() - // for (let i = 0; i < count; i++) { - // const text = (await dbNames.nth(i).textContent()) || '' - // if (text.includes(dbName)) { - // // Assuming deleteRowButton corresponds to a delete button for the row, - // // and we click the one at index i-1 (as per original logic) - // await this.deleteRowButton.nth(i - 1).click() - // await this.confirmDeleteButton.click() - // break - // } - // } - // } - // - // async clickOnEditDBByName(databaseName: string): Promise { - // const dbNames = this.dbNameList - // const count = await dbNames.count() - // for (let i = 0; i < count; i++) { - // const text = (await dbNames.nth(i).textContent()) || '' - // if (text.includes(databaseName)) { - // await this.editDatabaseButton.nth(i).click() - // break - // } - // } - // } - // - // async checkModulesInTooltip(moduleNameList: string[]): Promise { - // for (const item of moduleNameList) { - // await expect(this.moduleTooltip.locator('span', { hasText: `${item} v.` })).toBeVisible() - // } - // } - // - // async checkModulesOnPage(moduleList: Locator[]): Promise { - // for (const item of moduleList) { - // await expect(item).toBeVisible() - // } - // } - // - // async getAllDatabases(): Promise { - // const databases: string[] = [] - // await expect(this.dbNameList).toBeVisible() - // const n = await this.dbNameList.count() - // for (let k = 0; k < n; k++) { - // const name = await this.dbNameList.nth(k).textContent() - // databases.push(name || '') - // } - // return databases - // } - // - // async compareDatabases(actualList: string[], sortedList: string[]): Promise { - // for (let k = 0; k < actualList.length; k++) { - // await expect(actualList[k].trim()).toEqual(sortedList[k].trim()) - // } - // } - // - // async verifyDatabaseStatusIsVisible(databaseName: string): Promise { - // const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) - // const databaseNewPoint = this.page.getByTestId(`database-status-new-${databaseId}`) - // await expect(databaseNewPoint).toBeVisible() - // } - // - // async verifyDatabaseStatusIsNotVisible(databaseName: string): Promise { - // const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) - // const databaseEditBtn = this.page.getByTestId(`database-status-new-${databaseId}`) - // await expect(databaseEditBtn).not.toBeVisible() - // } - // - // getDatabaseNamesFromListByResult(listOfDb: DatabasesForImport, result: string): string[] { - // return listOfDb.filter(element => element.result === result).map(item => item.name!) - // } + + async deleteDatabaseByName(dbName: string): Promise { + const dbNames = this.tableRowContent + const count = await dbNames.count() + for (let i = 0; i < count; i++) { + const text = (await dbNames.nth(i).textContent()) || '' + if (text.includes(dbName)) { + // Assuming deleteRowButton corresponds to a delete button for the row, + // and we click the one at index i-1 (as per original logic) + await this.deleteRowButton.nth(i - 1).click() + await this.confirmDeleteButton.click() + break + } + } + } + + async clickOnEditDBByName(databaseName: string): Promise { + const dbNames = this.dbNameList + const count = await dbNames.count() + for (let i = 0; i < count; i++) { + const text = (await dbNames.nth(i).textContent()) || '' + if (text.includes(databaseName)) { + await this.editDatabaseButton.nth(i).click() + break + } + } + } + + async checkModulesInTooltip(moduleNameList: string[]): Promise { + for (const item of moduleNameList) { + await expect(this.moduleTooltip.locator('span', { hasText: `${item} v.` })).toBeVisible() + } + } + + async checkModulesOnPage(moduleList: Locator[]): Promise { + for (const item of moduleList) { + await expect(item).toBeVisible() + } + } + + async getAllDatabases(): Promise { + const databases: string[] = [] + await expect(this.dbNameList).toBeVisible() + const n = await this.dbNameList.count() + for (let k = 0; k < n; k++) { + const name = await this.dbNameList.nth(k).textContent() + databases.push(name || '') + } + return databases + } + + async compareDatabases(actualList: string[], sortedList: string[]): Promise { + for (let k = 0; k < actualList.length; k++) { + await expect(actualList[k].trim()).toEqual(sortedList[k].trim()) + } + } + + async verifyDatabaseStatusIsVisible(databaseName: string): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseNewPoint = this.page.getByTestId(`database-status-new-${databaseId}`) + await expect(databaseNewPoint).toBeVisible() + } + + async verifyDatabaseStatusIsNotVisible(databaseName: string): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseEditBtn = this.page.getByTestId(`database-status-new-${databaseId}`) + await expect(databaseEditBtn).not.toBeVisible() + } + + getDatabaseNamesFromListByResult(listOfDb: DatabasesForImport, result: string): string[] { + return listOfDb.filter(element => element.result === result).map(item => item.name!) + } } diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 9233fc703b..e1ee087e70 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -5,23 +5,17 @@ import {UserAgreementDialog} from '../pageObjects/dialogs/user-agreement-dialog' import {updateControlNumber} from '../helpers/electron/insights' import {RedisOverviewPage} from '../helpers/constants' import {RdiInstancesListPage} from '../pageObjects/rdi-instances-list-page' -// import {APIKeyRequests} from "../helpers/api/api-keys"; +import {DatabaseHelper} from "../helpers/database"; + let keyName: string let browserPage: BrowserPage -let userAgreementDialog: UserAgreementDialog +let databaseHelper: DatabaseHelper let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { - rdiInstancesListPage = new RdiInstancesListPage(electronPage) - userAgreementDialog = new UserAgreementDialog(electronPage) - await userAgreementDialog.acceptLicenseTerms() - await updateControlNumber(48.2, electronPage, workerState.apiUrl) -// Open default databases list tab if RDI opened - if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { - await myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) - } + await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) // keyName = Common.generateAlpanumeric(10) // browserPage = new BrowserPage(basePage) From c318126a2954be9fb592447666bc0152c620fe7c Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 16:31:58 +0200 Subject: [PATCH 040/128] wip PO --- tests/playwright/helpers/database.ts | 88 +++++++++---------- .../dialogs/add-redis-database-dialog.ts | 12 ++- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 9d0d3ec34e..5076647803 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -642,7 +642,7 @@ export class DatabaseHelper { apiUrl: string ): Promise { await this.acceptLicenseTerms(page,apiUrl) - await databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters) + await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters) // Reload Page to see the new added database through api await this.myRedisDatabasePage.reloadPage() // Connect to DB @@ -745,10 +745,10 @@ export class DatabaseHelper { // Accept License terms async acceptLicenseTerms(page: Page, apiUrl: string ): Promise { // await this.myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) - await userAgreementDialog.acceptLicenseTerms() + await this.userAgreementDialog.acceptLicenseTerms() await updateControlNumber(48.2, page, apiUrl) // Open default databases list tab if RDI opened - if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { + if (await this.rdiInstancesListPage.elementExistsLocator(this.rdiInstancesListPage.addRdiInstanceButton)) { await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) } // TODO delete after releasing chatbot @@ -758,62 +758,62 @@ export class DatabaseHelper { } // Accept License terms and connect to the RedisStack database - async acceptLicenseAndConnectToRedisStack(): Promise { - await this.acceptLicenseTerms() - // Connect to DB - await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() - await this.myRedisDatabasePage.addRedisDatabaseDialog.connectToRedisStackButton.click() - } + // async acceptLicenseAndConnectToRedisStack(): Promise { + // await this.acceptLicenseTerms() + // // Connect to DB + // await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() + // await this.myRedisDatabasePage.addRedisDatabaseDialog.connectToRedisStackButton.click() + // } /** * Delete database * @param databaseName The database name */ - async deleteDatabase(databaseName: string): Promise { - await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() - if ( - await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() - ) { - await this.deleteDatabaseByNameApi(databaseName) - } - } + // async deleteDatabase(databaseName: string): Promise { + // await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() + // if ( + // await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() + // ) { + // await this.deleteDatabaseByNameApi(databaseName) + // } + // } /** * Delete database with custom name * @param databaseName The database name - */ - async deleteCustomDatabase(databaseName: string): Promise { - await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() - if ( - await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() - ) { - await this.myRedisDatabasePage.deleteDatabaseByName(databaseName) - } - } + // */ + // async deleteCustomDatabase(databaseName: string): Promise { + // await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() + // if ( + // await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() + // ) { + // await this.myRedisDatabasePage.deleteDatabaseByName(databaseName) + // } + // } - /** - * Accept License terms and add database or connect to the Redis stask database - * @param databaseParameters The database parameters - * @param databaseName The database name - */ - async acceptTermsAddDatabaseOrConnectToRedisStack( - databaseParameters: AddNewDatabaseParameters - ): Promise { - if ( - await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() - ) { - await this.acceptLicenseTermsAndAddDatabase(databaseParameters) - } else { - await this.acceptLicenseAndConnectToRedisStack() - } - } + // /** + // * Accept License terms and add database or connect to the Redis stask database + // * @param databaseParameters The database parameters + // * @param databaseName The database name + // */ + // async acceptTermsAddDatabaseOrConnectToRedisStack( + // databaseParameters: AddNewDatabaseParameters + // ): Promise { + // if ( + // await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() + // ) { + // await this.acceptLicenseTermsAndAddDatabase(databaseParameters) + // } else { + // await this.acceptLicenseAndConnectToRedisStack() + // } + // } /** * Click on the edit database button by name * @param databaseName The name of the database */ async clickOnEditDatabaseByName(databaseName: string): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) const databaseEditBtn = this.myRedisDatabasePage.page.locator( `[data-testid=edit-instance-${databaseId}]` ) @@ -827,7 +827,7 @@ export class DatabaseHelper { * @param databaseName The name of the database */ async deleteDatabaseByNameApi(databaseName: string): Promise { - const databaseId = await databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) const databaseDeleteBtn = this.myRedisDatabasePage.page.locator( `[data-testid=delete-instance-${databaseId}-icon]` ) diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts index bc8172430d..df3404e58e 100644 --- a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts @@ -86,11 +86,14 @@ export class AddRedisDatabaseDialog extends BasePage{ private readonly timeoutInput: Locator // For certificate removal - private trashIconMsk(certificate: TlsCertificates): string { + aiChatMessage: Locator + aiCloseMessage: Locator + + trashIconMsk(certificate: TlsCertificates): string { return `[data-testid^="delete-${certificate}-cert"]` } - private getDeleteCertificate(certificate: TlsCertificates): Locator { + getDeleteCertificate(certificate: TlsCertificates): Locator { return this.page.locator(this.trashIconMsk(certificate)) } @@ -138,7 +141,10 @@ export class AddRedisDatabaseDialog extends BasePage{ this.sshPrivateKeyRadioBtn = page.locator('#privateKey ~ div') // LABELS - this.dataCompressorLabel = page.locator('[data-testid="showCompressor"] ~ label') + this.dataCompressorLabel = page.getByTestId('[data-testid="showCompressor"] ~ label') + this.aiChatMessage = page.getByTestId('ai-chat-message-btn') + this.aiCloseMessage = page.locator('[aria-label="Closes this modal window"]') + // SSH TEXT INPUTS this.sshHostInput = page.getByTestId('sshHost') From 90638575fc9500a768ee20e1599908839ed0bdde Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Mon, 17 Mar 2025 17:32:02 +0200 Subject: [PATCH 041/128] wip --- tests/playwright/fixtures/electron.ts | 46 +++++++++++-------- .../dialogs/user-agreement-dialog.ts | 4 +- .../playwright/tests/example.electron.spec.ts | 10 ++-- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts index b9ebde6880..1df4ac2797 100644 --- a/tests/playwright/fixtures/electron.ts +++ b/tests/playwright/fixtures/electron.ts @@ -35,23 +35,23 @@ export const test = base.extend { - - - // Set up the database before tests - // const dbApi = new DatabaseAPIRequests(workerState.apiUrl) - // await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) - - await use() // Run the tests - - - // Cleanup after all tests in this worker - - // throw new Error("test worker error") - // await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) - // close the app - await workerState.electronApp.close() - }, { scope: 'worker', auto: true }], + // forEachWorker: [async ({ workerState }, use) => { + // + // workerState.electronApp.firstWindow() + // // Set up the database before tests + // // const dbApi = new DatabaseAPIRequests(workerState.apiUrl) + // // await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) + // + // await use() // Run the tests + // + // + // // Cleanup after all tests in this worker + // + // // throw new Error("test worker error") + // // await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) + // // close the app + // await workerState.electronApp.close() + // }, { scope: 'worker', auto: true }], electronApp: async ({baseURL, workerState}, use) => { // Launch Electron App const electronApp = await electron.launch({ @@ -64,10 +64,18 @@ export const test = base.extend // This runs in the main Electron process, parameter here is always // the result of the require('electron') in the main app script. - app.getAppPath() + app.getAppPath() ) - console.log(appPath) + console.log(appPath) + // Get the first window that the app opens, wait if necessary. + const window = await electronApp.firstWindow(); + // Print the title. + console.log(await window.title()) + // Capture a screenshot. + await window.screenshot({ path: 'intro.png' }); + // Direct Electron console to Node terminal. + window.on('console', console.log); try { await use(electronApp) diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index 097893a7ce..6f51743170 100644 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -23,7 +23,9 @@ export class UserAgreementDialog extends BasePage { } async acceptLicenseTerms(): Promise { - if (await this.switchOptionEula.isVisible) { + let bl = await this.switchOptionEula.isVisible() + + if (await this.switchOptionEula.isVisible()) { await this.recommendedSwitcher.click() await this.switchOptionEula.click() await this.submitButton.click() diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index e1ee087e70..2908867d52 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -14,7 +14,7 @@ let databaseHelper: DatabaseHelper let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { - + databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) // keyName = Common.generateAlpanumeric(10) @@ -23,16 +23,16 @@ test.beforeEach(async ({electronPage, workerState}) => { }) -test.afterEach(async ({electronApp}) => { - console.log('WE ARE IN THE AFTER STEP') +test.afterEach(async ({electronApp, workerState}) => { + await workerState.electronApp.close() // const apiKeyClient = new APIKeyRequests(workerState.apiUrl) // await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) }) -test('basic test', async ({}) => { +test('basic test', async ({workerState}) => { + - console.log('WE ARE IN TEST') // await browserPage.addHashKey(keyName) // // // checks that the notification is displayed (should be in a different test) From 7ee658874f78c6a3fc51ef14da1911681eb91b26 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Tue, 18 Mar 2025 21:18:21 +0200 Subject: [PATCH 042/128] runninf a tat better --- tests/playwright/fixtures/electron.ts | 17 ++- tests/playwright/fixtures/simple-slectron.ts | 122 ++++++++++++++++++ tests/playwright/helpers/database.ts | 2 +- .../dialogs/user-agreement-dialog.ts | 2 +- tests/playwright/playwright.config.ts | 5 +- .../playwright/tests/example.electron.spec.ts | 5 +- 6 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 tests/playwright/fixtures/simple-slectron.ts diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts index 1df4ac2797..90bbd02e12 100644 --- a/tests/playwright/fixtures/electron.ts +++ b/tests/playwright/fixtures/electron.ts @@ -85,7 +85,22 @@ export const test = base.extend { - // Get the first window of the Electron app + let windows = []; + let elapsedTime = 0; + const maxWaitTime = 60000; // 60 seconds + const interval = 2000; // Check every 2 seconds + + while (windows.length === 0 && elapsedTime < maxWaitTime) { + await new Promise((resolve) => setTimeout(resolve, interval)); // Wait 2s + windows = await electronApp.windows(); // Check for open windows + elapsedTime += interval; + console.log(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); + + if (windows.length > 0) { + console.log(`✅ Found ${windows.length} window(s)!`); + break; + } + } const page = await electronApp.firstWindow() console.log('IN MAIN WINDOW') diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts new file mode 100644 index 0000000000..fcd47cedc4 --- /dev/null +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -0,0 +1,122 @@ +import { test as base, ElectronApplication, Page } from '@playwright/test'; +import { _electron as electron } from 'playwright'; +import { ossStandaloneConfig } from '../helpers/conf'; + +// Define shared state for worker scope +type WorkerSharedState = { + apiUrl: string; + dbConfig: typeof ossStandaloneConfig; + baseUrl: string; + electronApp: ElectronApplication; +}; + +type ElectronFixture = { + electronApp: ElectronApplication; + electronPage: Page; +}; + +async function launchElectronApp(baseUrl: string): Promise { + const electronApp = await electron.launch({ + userDataDir: './ELECTRON-UDATA', + executablePath: baseUrl, + args: ['index.html'], + timeout: 60000, + }); + // Capture Electron logs + electronApp.on('console', (msg) => { + console.log(`Electron Log: ${msg.type()} - ${msg.text()}`); + }); + + return electronApp; +} + +async function waitForWindows(electronApp: ElectronApplication, maxWaitTime = 60000, interval = 2000) { + let windows = []; + let elapsedTime = 0; + while (windows.length === 0 && elapsedTime < maxWaitTime) { + await new Promise((resolve) => setTimeout(resolve, interval)); + windows = await electronApp.windows(); + elapsedTime += interval; + console.log(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); + } + return windows; +} + +export const test = base.extend({ + workerState: [ + async ({}, use, testInfo) => { + console.log(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`); + const workerState: WorkerSharedState = { + apiUrl: testInfo.project.use.apiUrl, + dbConfig: ossStandaloneConfig, + baseUrl: testInfo.project.use.baseURL, + electronApp: null as any, + }; + await use(workerState); + }, + { scope: 'worker' }, + ], + + electronApp: async ({ workerState }, use) => { + console.log('🚀 Starting RedisInsight...'); + let electronApp = await launchElectronApp(workerState.baseUrl); + workerState.electronApp = electronApp; + console.log('⏳ Waiting for window...'); + let windows = await waitForWindows(electronApp); + + if (windows.length === 0) { + console.error('❌ No windows detected after 60s! Exiting.'); + await electronApp.close(); + return; + } + + console.log(`✅ Found ${windows.length} window(s)!`); + + if (windows.length === 2) { + console.log('⚠️ Detected two windows, closing all and restarting...'); + await Promise.all(windows.map(win => win.close())); + await electronApp.close(); + + console.log('🔄 Restarting RedisInsight...'); + electronApp = await launchElectronApp(workerState.baseUrl); + workerState.electronApp = electronApp; + + windows = await waitForWindows(electronApp); + console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); + } + + await use(electronApp); + }, + + electronPage: async ({ electronApp,workerState }, use) => { + let windows = await waitForWindows(electronApp); + if (windows.length === 0) { + console.error('❌ No windows detected! Stopping test.'); + await electronApp.close(); + return; + } + if (windows.length === 2) { + console.log('⚠️ Detected two windows, closing all and restarting...'); + await Promise.all(windows.map(win => win.close())); + await electronApp.close(); + console.log('🔄 Restarting RedisInsight...'); + electronApp = await launchElectronApp(workerState.baseUrl); + workerState.electronApp = electronApp; + windows = await waitForWindows(electronApp); + console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); + } + if (windows.length === 2) { + console.log('⚠️ Detected two windows, closing all and stopping test.'); + await Promise.all(windows.map(win => win.close())); + await electronApp.close(); + + return; + } + const window = windows[0]; + await window.waitForLoadState('domcontentloaded'); + console.log(`🖥️ Window Title: ${await window.title()}`); + await use(window); + }, +}); + +export { expect } from '@playwright/test'; diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 5076647803..2164a528c8 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -746,7 +746,7 @@ export class DatabaseHelper { async acceptLicenseTerms(page: Page, apiUrl: string ): Promise { // await this.myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) await this.userAgreementDialog.acceptLicenseTerms() - await updateControlNumber(48.2, page, apiUrl) + // await updateControlNumber(48.2, page, apiUrl) // Open default databases list tab if RDI opened if (await this.rdiInstancesListPage.elementExistsLocator(this.rdiInstancesListPage.addRdiInstanceButton)) { await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index 6f51743170..2a5c5f63bf 100644 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -23,7 +23,7 @@ export class UserAgreementDialog extends BasePage { } async acceptLicenseTerms(): Promise { - let bl = await this.switchOptionEula.isVisible() + if (await this.switchOptionEula.isVisible()) { await this.recommendedSwitcher.click() diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 10888bc220..77c1ca11cc 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -20,7 +20,7 @@ export type TestOptions = { export default defineConfig({ testDir: './tests', /* Maximum time one test can run for. */ - timeout: 300 * 1000, + timeout: 600 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. @@ -112,7 +112,8 @@ export default defineConfig({ name: 'localElectron', testMatch: ['**.electron.spec.ts'], use: { - baseURL: '/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight', + baseURL: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', + // baseURL: '/usr/bin/redisinsight', apiUrl: process.env.API_URL || 'https://localhost:5540/api', headless: false }, diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 2908867d52..6e9205ab1a 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,4 +1,4 @@ -import {test, expect} from '../fixtures/electron' +import {test, expect} from '../fixtures/simple-slectron' // import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' import {UserAgreementDialog} from '../pageObjects/dialogs/user-agreement-dialog' @@ -16,7 +16,7 @@ test.beforeEach(async ({electronPage, workerState}) => { databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) - + await electronPage.getByText('Add Redis').click() // keyName = Common.generateAlpanumeric(10) // browserPage = new BrowserPage(basePage) @@ -47,3 +47,4 @@ test('basic test', async ({workerState}) => { }) + From 072b20e99bf03dbd99fe1cc16885e1802929a3a7 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Tue, 18 Mar 2025 22:13:18 +0200 Subject: [PATCH 043/128] web test back working --- tests/playwright/fixtures/open-ri.ts | 2 +- tests/playwright/fixtures/simple-slectron.ts | 15 ++++++++------- tests/playwright/helpers/api/api-info.ts | 4 ++-- tests/playwright/helpers/database.ts | 2 +- tests/playwright/package.json | 1 + tests/playwright/tests/example.electron.spec.ts | 3 +++ 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 9ca138daf0..a16b774e0d 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -1,5 +1,5 @@ import { test as base, Page } from '@playwright/test' -import BasePage from '../pageObjects/base-page' +import { BasePage } from '../pageObjects/base-page' import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialog' import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { ossStandaloneConfig } from '../helpers/conf' diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index fcd47cedc4..bc9c56b906 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -2,6 +2,7 @@ import { test as base, ElectronApplication, Page } from '@playwright/test'; import { _electron as electron } from 'playwright'; import { ossStandaloneConfig } from '../helpers/conf'; + // Define shared state for worker scope type WorkerSharedState = { apiUrl: string; @@ -105,13 +106,13 @@ export const test = base.extend win.close())); - await electronApp.close(); - - return; - } + // if (windows.length === 2) { + // console.log('⚠️ Detected two windows, closing all and stopping test.'); + // await Promise.all(windows.map(win => win.close())); + // await electronApp.close() + // + // return; + // } const window = windows[0]; await window.waitForLoadState('domcontentloaded'); console.log(`🖥️ Window Title: ${await window.title()}`); diff --git a/tests/playwright/helpers/api/api-info.ts b/tests/playwright/helpers/api/api-info.ts index b914f5879c..089bedfdf5 100644 --- a/tests/playwright/helpers/api/api-info.ts +++ b/tests/playwright/helpers/api/api-info.ts @@ -8,7 +8,7 @@ import { ResourcePath } from '../constants' export async function syncFeaturesApi(apiUrl: string): Promise { const apiClient = new HttpClient(apiUrl).getClient() const response = await apiClient.post(ResourcePath.SyncFeatures, {}) - if (!response) { - throw new Error('Failed to synchronize features: Empty response') + if (response.status !== 200) { + throw new Error('Failed to synchronize features') } } diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 2164a528c8..5076647803 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -746,7 +746,7 @@ export class DatabaseHelper { async acceptLicenseTerms(page: Page, apiUrl: string ): Promise { // await this.myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) await this.userAgreementDialog.acceptLicenseTerms() - // await updateControlNumber(48.2, page, apiUrl) + await updateControlNumber(48.2, page, apiUrl) // Open default databases list tab if RDI opened if (await this.rdiInstancesListPage.elementExistsLocator(this.rdiInstancesListPage.addRdiInstanceButton)) { await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) diff --git a/tests/playwright/package.json b/tests/playwright/package.json index a5b1696372..af47fa4f4f 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -16,6 +16,7 @@ "allTests": "playwright test", "generateReports": "allure generate --clean", "test:chromium": "playwright test --project=localChromium", + "test:chromium-debug": "playwright test --project=localChromium --debug", "test:electron": "playwright test --project=localElectron", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 6e9205ab1a..f752b88bcc 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -14,6 +14,9 @@ let databaseHelper: DatabaseHelper let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { + await electronPage.reload() ; + + databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) await electronPage.getByText('Add Redis').click() From 35fe22a10d4c82c220fc902d9ecc40f6e11cb809 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Tue, 18 Mar 2025 23:01:56 +0200 Subject: [PATCH 044/128] sql lite issues --- tests/playwright/fixtures/simple-slectron.ts | 8 + tests/playwright/helpers/database.ts | 460 +----------------- tests/playwright/helpers/electron/insights.ts | 4 +- tests/playwright/package.json | 2 + .../playwright/tests/example.electron.spec.ts | 2 +- tests/playwright/yarn.lock | 5 + 6 files changed, 22 insertions(+), 459 deletions(-) diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index bc9c56b906..bd72f3677c 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -1,6 +1,8 @@ import { test as base, ElectronApplication, Page } from '@playwright/test'; import { _electron as electron } from 'playwright'; import { ossStandaloneConfig } from '../helpers/conf'; +import {updateControlNumber} from "../helpers/electron/insights"; +import {DatabaseHelper} from "../helpers/database"; // Define shared state for worker scope @@ -53,6 +55,8 @@ export const test = base.extend { console.log('🚀 Starting RedisInsight...'); + + // update control nmb + await updateControlNumber(48.2, workerState.apiUrl); + let electronApp = await launchElectronApp(workerState.baseUrl); workerState.electronApp = electronApp; console.log('⏳ Waiting for window...'); diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 5076647803..814634ce55 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -1,457 +1,3 @@ -// // import { Selector, t } from 'testcafe'; -// import { DatabaseAPIRequests } from './api/api-database' -// import { RedisOverviewPage } from './constants' -// import { updateControlNumber } from './insights' -// import { -// AddNewDatabaseParameters, -// SentinelParameters, -// OSSClusterParameters -// } from '../pageObjects/dialogs/add-redis-database-dialog' -// import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' -// import { -// this.myRedisDatabasePage, -// BrowserPage, -// AutoDiscoverREDatabases -// } from '../pageObjects' -// import { UserAgreementDialog } from '../pageObjects/dialogs' -// import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' -// -// const this.myRedisDatabasePage = new this.myRedisDatabasePage() -// const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() -// const autoDiscoverREDatabases = new AutoDiscoverREDatabases() -// const browserPage = new BrowserPage() -// const userAgreementDialog = new UserAgreementDialog() -// const databaseAPIRequests = new DatabaseAPIRequests() -// const rdiInstancesListPage = new RdiInstancesListPage() -// -// export class DatabaseHelper { -// /** -// * Add a new database manually using host and port -// * @param databaseParameters The database parameters -// */ -// async addNewStandaloneDatabase( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// // Fill the add database form -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( -// databaseParameters -// ) -// // Click for saving -// await t -// .click(this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton) -// // Wait for database to be exist -// .expect( -// this.myRedisDatabasePage.dbNameList.withExactText( -// databaseParameters.databaseName ?? '' -// ).exists -// ) -// .ok('The database not displayed', { timeout: 10000 }) -// // Close message -// .click(this.myRedisDatabasePage.Toast.toastCloseButton) -// } -// -// /** -// * Add a new database via autodiscover using Sentinel option -// * @param databaseParameters The Sentinel parameters: host, port and sentinel password -// */ -// async discoverSentinelDatabase( -// databaseParameters: SentinelParameters -// ): Promise { -// // Fill sentinel parameters to auto-discover Master Groups -// await this.myRedisDatabasePage.addRedisDatabaseDialog.discoverSentinelDatabases( -// databaseParameters -// ) -// // Click for autodiscover -// await t -// .click( -// this.myRedisDatabasePage.AddRedisDatabaseDialog -// .addRedisDatabaseButton -// ) -// .expect(discoverMasterGroupsPage.addPrimaryGroupButton.exists) -// .ok('User is not on the second step of Sentinel flow', { -// timeout: 10000 -// }) -// // Select Master Groups and Add to Redis Insight -// await discoverMasterGroupsPage.addMasterGroups() -// await t.click(autoDiscoverREDatabases.viewDatabasesButton) -// } -// -// /** -// * Add a new database from RE Cluster via auto-discover flow -// * @param databaseParameters The database parameters -// */ -// async addNewREClusterDatabase( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// // Fill the add database form -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverREClusterDatabase( -// databaseParameters -// ) -// // Click on submit button -// await t -// .click(this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton) -// // Wait for database to be exist in the list of Autodiscover databases and select it -// .expect( -// autoDiscoverREDatabases.databaseName.withExactText( -// databaseParameters.databaseName ?? '' -// ).exists -// ) -// .ok('The database not displayed', { timeout: 10000 }) -// .typeText( -// autoDiscoverREDatabases.search, -// databaseParameters.databaseName ?? '' -// ) -// .click(autoDiscoverREDatabases.databaseCheckbox) -// // Click Add selected databases button -// .click(autoDiscoverREDatabases.addSelectedDatabases) -// .click(autoDiscoverREDatabases.viewDatabasesButton) -// } -// -// /** -// * Add a new database from OSS Cluster via auto-discover flow -// * @param databaseParameters The database parameters -// */ -// async addOSSClusterDatabase( -// databaseParameters: OSSClusterParameters -// ): Promise { -// // Enter required parameters for OSS Cluster -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addOssClusterDatabase( -// databaseParameters -// ) -// // Click for saving -// await t -// .click(this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton) -// // Check for info message that DB was added -// .expect(this.myRedisDatabasePage.Toast.toastHeader.exists) -// .ok('Info message not exists', { timeout: 10000 }) -// // Wait for database to be exist -// .expect( -// this.myRedisDatabasePage.dbNameList.withExactText( -// databaseParameters.ossClusterDatabaseName -// ).exists -// ) -// .ok('The database not displayed', { timeout: 10000 }) -// } -// -// /** -// * Add a new database from Redis Cloud via auto-discover flow -// * @param cloudAPIAccessKey The Cloud API Access Key -// * @param cloudAPISecretKey The Cloud API Secret Key -// */ -// async autodiscoverRECloudDatabase( -// cloudAPIAccessKey: string, -// cloudAPISecretKey: string -// ): Promise { -// // Fill the add database form and Submit -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverRECloudDatabase( -// cloudAPIAccessKey, -// cloudAPISecretKey -// ) -// await t.click( -// this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton -// ) -// await t -// .expect( -// autoDiscoverREDatabases.title.withExactText( -// 'Redis Cloud Subscriptions' -// ).exists -// ) -// .ok('Subscriptions list not displayed', { timeout: 120000 }) -// // Select subscriptions -// await t.click(this.myRedisDatabasePage.addRedisDatabaseDialog.selectAllCheckbox) -// await t.click(this.myRedisDatabasePage.addRedisDatabaseDialog.showDatabasesButton) -// // Select databases for adding -// const databaseName = await autoDiscoverREDatabases.getDatabaseName() -// await t.click(autoDiscoverREDatabases.databaseCheckbox) -// await t.click(autoDiscoverREDatabases.addSelectedDatabases) -// // Wait for database to be exist in the redis databases list -// await t -// .expect( -// autoDiscoverREDatabases.title.withExactText( -// 'Redis Enterprise Databases Added' -// ).exists -// ) -// .ok('Added databases list not displayed', { timeout: 20000 }) -// await t.click(autoDiscoverREDatabases.viewDatabasesButton) -// // uncomment when fixed db will be added to cloud subscription -// // await t.expect(this.myRedisDatabasePage.dbNameList.withExactText(databaseName).exists).ok('The database not displayed', { timeout: 10000 }); -// return databaseName -// } -// -// /** -// * Accept License terms and add database -// * @param databaseParameters The database parameters -// * @param databaseName The database name -// */ -// async acceptLicenseTermsAndAddDatabase( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// await this.acceptLicenseTerms() -// await this.addNewStandaloneDatabase(databaseParameters) -// // Connect to DB -// await this.myRedisDatabasePage.clickOnDBByName( -// databaseParameters.databaseName! -// ) -// } -// -// /** -// * Accept License terms and add database using api -// * @param databaseParameters The database parameters -// * @param databaseName The database name -// */ -// async acceptLicenseTermsAndAddDatabaseApi( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// await this.acceptLicenseTerms() -// await databaseAPIRequests.addNewStandaloneDatabaseApi( -// databaseParameters -// ) -// // Reload Page to see the new added database through api -// await this.myRedisDatabasePage.reloadPage() -// // Connect to DB -// await this.myRedisDatabasePage.clickOnDBByName( -// databaseParameters.databaseName! -// ) -// } -// -// /** -// * Accept License terms and add OSS cluster database -// * @param databaseParameters The database parameters -// * @param databaseName The database name -// */ -// async acceptLicenseTermsAndAddOSSClusterDatabase( -// databaseParameters: OSSClusterParameters -// ): Promise { -// await this.acceptLicenseTerms() -// await this.addOSSClusterDatabase(databaseParameters) -// // Connect to DB -// await this.myRedisDatabasePage.clickOnDBByName( -// databaseParameters.ossClusterDatabaseName! -// ) -// } -// -// /** -// * Accept License terms and add Sentinel database using api -// * @param databaseParameters The database parameters -// */ -// async acceptLicenseTermsAndAddSentinelDatabaseApi( -// databaseParameters: SentinelParameters -// ): Promise { -// await this.acceptLicenseTerms() -// await databaseAPIRequests.discoverSentinelDatabaseApi( -// databaseParameters -// ) -// // Reload Page to see the database added through api -// await this.myRedisDatabasePage.reloadPage() -// // Connect to DB -// await this.myRedisDatabasePage.clickOnDBByName( -// databaseParameters.masters![1].alias ?? '' -// ) -// } -// -// /** -// * Accept License terms and add RE Cluster database -// * @param databaseParameters The database parameters -// */ -// async acceptLicenseTermsAndAddREClusterDatabase( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// await this.acceptLicenseTerms() -// await this.addNewREClusterDatabase(databaseParameters) -// // Connect to DB -// await this.myRedisDatabasePage.clickOnDBByName( -// databaseParameters.databaseName ?? '' -// ) -// } -// -// /** -// * Accept License terms and add RE Cloud database -// * @param databaseParameters The database parameters -// */ -// async acceptLicenseTermsAndAddRECloudDatabase( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// const searchTimeout = 60 * 1000 // 60 sec to wait database appearing -// const dbSelector = this.myRedisDatabasePage.dbNameList.withExactText( -// databaseParameters.databaseName ?? '' -// ) -// const startTime = Date.now() -// -// await this.acceptLicenseTerms() -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( -// databaseParameters -// ) -// // Click for saving -// await t.click( -// this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton -// ) -// await t.wait(3000) -// // Reload page until db appears -// do { -// await this.myRedisDatabasePage.reloadPage() -// } while ( -// !(await dbSelector.exists) && -// Date.now() - startTime < searchTimeout -// ) -// await t -// .expect( -// this.myRedisDatabasePage.dbNameList.withExactText( -// databaseParameters.databaseName ?? '' -// ).exists -// ) -// .ok('The database not displayed', { timeout: 5000 }) -// await this.myRedisDatabasePage.clickOnDBByName( -// databaseParameters.databaseName ?? '' -// ) -// await t -// .expect(browserPage.keysSummary.exists) -// .ok('Key list not loaded', { timeout: 15000 }) -// } -// -// /** -// * Add RE Cloud database -// * @param databaseParameters The database parameters -// */ -// async addRECloudDatabase( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// const searchTimeout = 60 * 1000 // 60 sec to wait database appearing -// const dbSelector = this.myRedisDatabasePage.dbNameList.withExactText( -// databaseParameters.databaseName ?? '' -// ) -// const startTime = Date.now() -// -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( -// databaseParameters -// ) -// // Click for saving -// await t.click( -// this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton -// ) -// await t.wait(3000) -// // Reload page until db appears -// do { -// await this.myRedisDatabasePage.reloadPage() -// } while ( -// !(await dbSelector.exists) && -// Date.now() - startTime < searchTimeout -// ) -// await t -// .expect( -// this.myRedisDatabasePage.dbNameList.withExactText( -// databaseParameters.databaseName ?? '' -// ).exists -// ) -// .ok('The database not displayed', { timeout: 5000 }) -// } -// -// // Accept License terms -// async acceptLicenseTerms(): Promise { -// // await t.maximizeWindow() -// await userAgreementDialog.acceptLicenseTerms() -// await updateControlNumber(48.2) -// // Open default databases list tab if RDI opened -// if (await rdiInstancesListPage.elementExistsLocator(rdiInstancesListPage.addRdiInstanceButton)) { -// await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) -// } -// // TODO delete after releasing chatbot -// if (await this.myRedisDatabasePage.addRedisDatabaseDialog.aiChatMessage.exists) { -// await t.click(this.myRedisDatabasePage.addRedisDatabaseDialog.aiCloseMessage) -// } -// } -// -// // Accept License terms and connect to the RedisStack database -// async acceptLicenseAndConnectToRedisStack(): Promise { -// await this.acceptLicenseTerms() -// // Connect to DB -// await t -// .click(this.myRedisDatabasePage.NavigationPanel.myRedisDBButton) -// .click( -// this.myRedisDatabasePage.addRedisDatabaseDialog.connectToRedisStackButton -// ) -// } -// -// /** -// * Delete database -// * @param databaseName The database name -// */ -// async deleteDatabase(databaseName: string): Promise { -// await t.click(this.myRedisDatabasePage.NavigationPanel.myRedisDBButton) -// if ( -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.exists -// ) { -// await this.deleteDatabaseByNameApi(databaseName) -// } -// } -// -// /** -// * Delete database with custom name -// * @param databaseName The database name -// */ -// async deleteCustomDatabase(databaseName: string): Promise { -// await t.click(this.myRedisDatabasePage.NavigationPanel.myRedisDBButton) -// if ( -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.exists -// ) { -// await this.myRedisDatabasePage.deleteDatabaseByName(databaseName) -// } -// } -// -// /** -// * Accept License terms and add database or connect to the Redis stask database -// * @param databaseParameters The database parameters -// * @param databaseName The database name -// */ -// async acceptTermsAddDatabaseOrConnectToRedisStack( -// databaseParameters: AddNewDatabaseParameters -// ): Promise { -// if ( -// await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.exists -// ) { -// await this.acceptLicenseTermsAndAddDatabase(databaseParameters) -// } -// else { -// await this.acceptLicenseAndConnectToRedisStack() -// } -// } -// -// /** -// * Click on the edit database button by name -// * @param databaseName The name of the database -// */ -// async clickOnEditDatabaseByName(databaseName: string): Promise { -// const databaseId = await databaseAPIRequests.getDatabaseIdByName( -// databaseName -// ) -// const databaseEditBtn = Selector( -// `[data-testid=edit-instance-${databaseId}]` -// ) -// -// await t -// .expect(databaseEditBtn.exists) -// .ok(`"${databaseName}" database not displayed`) -// await t.click(databaseEditBtn) -// } -// -// /** -// * Delete database button by name -// * @param databaseName The name of the database -// */ -// async deleteDatabaseByNameApi(databaseName: string): Promise { -// const databaseId = await databaseAPIRequests.getDatabaseIdByName( -// databaseName -// ) -// const databaseDeleteBtn = Selector( -// `[data-testid=delete-instance-${databaseId}-icon]` -// ) -// -// await t -// .expect(databaseDeleteBtn.exists) -// .ok(`"${databaseName}" database not displayed`) -// await t.click(databaseDeleteBtn) -// await t.click(this.myRedisDatabasePage.confirmDeleteButton) -// } -// } import {expect, Page} from '@playwright/test' import { DatabaseAPIRequests } from './api/api-databases' import { RedisOverviewPage } from './constants' @@ -470,9 +16,10 @@ import { AutoDiscoverREDatabases, AddRedisDatabaseDialog } from '../pageObjects' +import {BasePage} from "../pageObjects/base-page"; -export class DatabaseHelper { +export class DatabaseHelper extends BasePage{ private myRedisDatabasePage: MyRedisDatabasePage private addRedisDataBaseDialog: AddRedisDatabaseDialog @@ -484,6 +31,7 @@ export class DatabaseHelper { private rdiInstancesListPage: RdiInstancesListPage constructor(page: Page, apiUrl: string) { + super(page) this.addRedisDataBaseDialog = new AddRedisDatabaseDialog(page) this.autoDiscoverREDatabases = new AutoDiscoverREDatabases(page) this.browserPage = new BrowserPage(page) @@ -746,7 +294,7 @@ export class DatabaseHelper { async acceptLicenseTerms(page: Page, apiUrl: string ): Promise { // await this.myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) await this.userAgreementDialog.acceptLicenseTerms() - await updateControlNumber(48.2, page, apiUrl) + await updateControlNumber(48.2, apiUrl) // Open default databases list tab if RDI opened if (await this.rdiInstancesListPage.elementExistsLocator(this.rdiInstancesListPage.addRdiInstanceButton)) { await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) diff --git a/tests/playwright/helpers/electron/insights.ts b/tests/playwright/helpers/electron/insights.ts index 224751b02a..9382d96e35 100755 --- a/tests/playwright/helpers/electron/insights.ts +++ b/tests/playwright/helpers/electron/insights.ts @@ -33,11 +33,11 @@ export async function modifyFeaturesConfigJson(filePath: string): Promise * @param controlNumber Control number to update * @param page Playwright page instance */ -export async function updateControlNumber(controlNumber: number, page: Page, apiUrl: string): Promise { +export async function updateControlNumber(controlNumber: number, apiUrl: string): Promise { await syncFeaturesApi(apiUrl) await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }) await syncFeaturesApi(apiUrl) - await page.reload() + } /** diff --git a/tests/playwright/package.json b/tests/playwright/package.json index af47fa4f4f..bb10b7de0f 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -18,6 +18,7 @@ "test:chromium": "playwright test --project=localChromium", "test:chromium-debug": "playwright test --project=localChromium --debug", "test:electron": "playwright test --project=localElectron", + "test:electron-debug": "playwright test --project=localElectron", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", @@ -28,6 +29,7 @@ "dotenv": "^16.4.7", "dotenv-cli": "^8.0.0", "fs-extra": "^11.3.0", + "node-color-log": "^12.0.1", "sqlite3": "^5.1.7" } } diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index f752b88bcc..f7a283ae2a 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -14,7 +14,7 @@ let databaseHelper: DatabaseHelper let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { - await electronPage.reload() ; + databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index b722e6e3a9..4044561bda 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -653,6 +653,11 @@ node-addon-api@^7.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== +node-color-log@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/node-color-log/-/node-color-log-12.0.1.tgz#47d982e3cb6aa90c2936ca38cd910ef82076c6f5" + integrity sha512-fhbWy00HXAVucPHoji9KNZRtXHcDKuMoVJ3QA+vaMEcAyK6psmJAf5TF9t2SmkybuHz0jre+jgUDyXcFmpgSNg== + node-gyp@8.x: version "8.4.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" From 8171b5d7a0f5e37dd302840e3bae11834c55568a Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Wed, 19 Mar 2025 13:21:15 +0200 Subject: [PATCH 045/128] sql lite issues wip --- tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts b/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts index 18b457f1fa..6dfa487e15 100644 --- a/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts +++ b/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts @@ -35,7 +35,7 @@ test('Verify that user can add Hash Key', async t => { await browserPage.searchByKeyName(keyName); const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); -}); +}).only; test('Verify that user can add Set Key', async t => { keyName = Common.generateWord(10); // Add Set key From a7799fbe54e2c797adfc5908ea2b203462dd0cf5 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Wed, 19 Mar 2025 14:42:12 +0200 Subject: [PATCH 046/128] some env creation improvements --- .../tests/web/smoke/browser/add-keys.e2e.ts | 2 +- .../create_local_environment.sh | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts b/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts index 27fa2e24e2..4254158c8f 100644 --- a/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts +++ b/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts @@ -35,7 +35,7 @@ test('Verify that user can add Hash Key', async t => { await browserPage.searchByKeyName(keyName); const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); -}); +}).only test('Verify that user can add Set Key', async t => { keyName = Common.generateWord(10); // Add Set key diff --git a/tests/playwright/local-docker-environment/create_local_environment.sh b/tests/playwright/local-docker-environment/create_local_environment.sh index bec69e9ef9..d127bdb9ab 100755 --- a/tests/playwright/local-docker-environment/create_local_environment.sh +++ b/tests/playwright/local-docker-environment/create_local_environment.sh @@ -1,7 +1,23 @@ #!/bin/bash +# Create directory and set write permissions mkdir -p "rihomedir" chmod +w rihomedir/ -docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml up -d +# Display options to the user +echo "Choose an option:" +echo "1) Start Redis databases without Redis Insight UI" +echo "2) Start Redis databases and Redis Insight UI - docker container must be loaded" +read -p "Enter your choice [1 or 2]: " choice +# Execute based on user choice +if [ "$choice" == "1" ]; then + echo "Starting environment without Redis Insight UI..." + docker compose -p test-docker -f rte.docker-compose.yml up -d +elif [ "$choice" == "2" ]; then + echo "Starting environment with Redis Insight UI..." + docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml up -d +else + echo "Invalid option. Please enter 1 or 2." + exit 1 +fi From 26e364936fad7fdddf7798b03291c8d7a25cabd8 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Wed, 19 Mar 2025 22:32:17 +0200 Subject: [PATCH 047/128] update control Numbers passing - fix other requests next --- tests/playwright/.desktop.env | 53 +++++++++++++++ tests/playwright/fixtures/simple-slectron.ts | 41 +++++++----- tests/playwright/helpers/api/api-info.ts | 8 ++- tests/playwright/helpers/api/http-client.ts | 64 +++++++++++-------- tests/playwright/helpers/common.ts | 33 +++++----- tests/playwright/helpers/database.ts | 5 +- tests/playwright/helpers/electron/insights.ts | 6 +- tests/playwright/pageObjects/base-page.ts | 11 ++++ tests/playwright/playwright.config.ts | 6 +- .../playwright/tests/example.electron.spec.ts | 4 ++ 10 files changed, 162 insertions(+), 69 deletions(-) create mode 100644 tests/playwright/.desktop.env diff --git a/tests/playwright/.desktop.env b/tests/playwright/.desktop.env new file mode 100644 index 0000000000..7008dda6ba --- /dev/null +++ b/tests/playwright/.desktop.env @@ -0,0 +1,53 @@ +#COMMON_URL=/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/resources/app.asar/dist/renderer/index.html +#API_URL=https://localhost:5530/api +OSS_SENTINEL_PASSWORD=password +RI_APP_FOLDER_NAME=.redis-insight-stage + +OSS_STANDALONE_HOST=localhost +OSS_STANDALONE_PORT=8100 + +OSS_STANDALONE_V5_HOST=localhost +OSS_STANDALONE_V5_PORT=8101 + +OSS_STANDALONE_V7_HOST=localhost +OSS_STANDALONE_V7_PORT=8108 + +OSS_STANDALONE_V8_HOST=localhost +OSS_STANDALONE_V8_PORT=8109 + +OSS_STANDALONE_REDISEARCH_HOST=localhost +OSS_STANDALONE_REDISEARCH_PORT=8102 + +OSS_STANDALONE_BIG_HOST=localhost +OSS_STANDALONE_BIG_PORT=8103 + +OSS_STANDALONE_TLS_HOST=localhost +OSS_STANDALONE_TLS_PORT=8104 + +OSS_STANDALONE_EMPTY_HOST=localhost +OSS_STANDALONE_EMPTY_PORT=8105 + +OSS_STANDALONE_REDISGEARS_HOST=localhost +OSS_STANDALONE_REDISGEARS_PORT=8106 + +OSS_STANDALONE_NOPERM_HOST=localhost +OSS_STANDALONE_NOPERM_PORT=8100 + +OSS_CLUSTER_REDISGEARS_2_HOST=localhost +OSS_CLUSTER_REDISGEARS_2_PORT=8107 + +OSS_CLUSTER_HOST=localhost +OSS_CLUSTER_PORT=8200 + +OSS_SENTINEL_HOST=localhost +OSS_SENTINEL_PORT=28100 + +RE_CLUSTER_HOST=localhost +RE_CLUSTER_PORT=19443 + +RI_NOTIFICATION_UPDATE_URL=https://s3.amazonaws.com/redisinsight.test/public/tests/e2e/notifications.json +RI_NOTIFICATION_SYNC_INTERVAL=30000 + +RI_FEATURES_CONFIG_URL=http://localhost:5551/remote/features-config.json +RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 +REMOTE_FOLDER_PATH=/home/runner/work/RedisInsight/RedisInsight/tests/e2e/remote diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index bd72f3677c..70fbeaffa7 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -66,7 +66,7 @@ export const test = base.extend win.close())); - await electronApp.close(); - - console.log('🔄 Restarting RedisInsight...'); - electronApp = await launchElectronApp(workerState.baseUrl); - workerState.electronApp = electronApp; - - windows = await waitForWindows(electronApp); - console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); + // await Promise.all(windows.map(win => win.close())); + // await electronApp.close(); + // + // console.log('🔄 Restarting RedisInsight...'); + // electronApp = await launchElectronApp(workerState.baseUrl); + // workerState.electronApp = electronApp; + // + // windows = await waitForWindows(electronApp); + // console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); } await use(electronApp); @@ -99,6 +99,7 @@ export const test = base.extend { let windows = await waitForWindows(electronApp); + if (windows.length === 0) { console.error('❌ No windows detected! Stopping test.'); await electronApp.close(); @@ -106,13 +107,18 @@ export const test = base.extend win.close())); - await electronApp.close(); - console.log('🔄 Restarting RedisInsight...'); - electronApp = await launchElectronApp(workerState.baseUrl); - workerState.electronApp = electronApp; - windows = await waitForWindows(electronApp); - console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); + // await Promise.all(windows.map(win => win.close())); + // await electronApp.close(); + // console.log('🔄 Restarting RedisInsight...'); + // electronApp = await launchElectronApp(workerState.baseUrl); + // workerState.electronApp = electronApp; + // windows = await waitForWindows(electronApp); + // console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); + + let titles = (await Promise.all(windows.map(async page => ({ + page, + title: await page.title() })))).find(entry => entry.title)?.page || null; + } // if (windows.length === 2) { // console.log('⚠️ Detected two windows, closing all and stopping test.'); @@ -122,6 +128,7 @@ export const test = base.extend { +export async function syncFeaturesApi(apiUrl: string, xWindowsId: string): Promise { const apiClient = new HttpClient(apiUrl).getClient() - const response = await apiClient.post(ResourcePath.SyncFeatures, {}) + const response = await apiClient.post(ResourcePath.SyncFeatures, {}, + {headers:{ + 'X-Window-Id': xWindowsId + } + }) if (response.status !== 200) { throw new Error('Failed to synchronize features') } diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index 57db62b894..37050b3513 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -1,39 +1,49 @@ -import axios, { AxiosInstance } from 'axios' +import axios, { AxiosInstance } from 'axios'; -export class HttpClient { - private apiUrl: string - private apiClient: AxiosInstance - private static instance: HttpClient +interface CustomAxiosInstance extends AxiosInstance { + setHeaders: (headers: Record) => void; +} + +export class HttpClient { + private apiUrl: string; + private apiClient: CustomAxiosInstance; - constructor(apiUrl: string) { - this.apiUrl = apiUrl + constructor(apiUrl: string, ) { + this.apiUrl = apiUrl; this.apiClient = axios.create({ - baseURL: this.apiUrl, // Change to your API base URL + baseURL: this.apiUrl, + headers: {}, httpsAgent: new (require('https').Agent)({ - rejectUnauthorized: false // Allows self-signed/invalid SSL certs - }) - }) + rejectUnauthorized: false, // Allows self-signed/invalid SSL certs + }), + }) as CustomAxiosInstance; - // Enable logging if DEBUG=1 is set - if (process.env.DEBUG === '1') { + // Attach setHeaders method to allow setting headers dynamically + this.apiClient.setHeaders = (headers: Record) => { + Object.assign(this.apiClient.defaults.headers.common, headers); + }; + + // Enable logging if DEBUG is set + if (process.env.DEBUG) { this.apiClient.interceptors.request.use(request => { - console.log('Starting Request', request) - return request - }) - this.apiClient.interceptors.response.use(response => { - console.log('Response:', response) - return response - }, error => { - console.error('Error Response:', error.response) - return Promise.reject(error) - }) + console.log('Starting Request', request); + return request; + }); + this.apiClient.interceptors.response.use( + response => { + console.log('Response:', response); + return response; + }, + error => { + console.error('Error Response:', error.response); + return Promise.reject(error); + } + ); } } - getClient(): AxiosInstance { - return this.apiClient + getClient(): CustomAxiosInstance { + return this.apiClient; } - - } diff --git a/tests/playwright/helpers/common.ts b/tests/playwright/helpers/common.ts index 2502524d08..8d31ad72f0 100644 --- a/tests/playwright/helpers/common.ts +++ b/tests/playwright/helpers/common.ts @@ -4,17 +4,17 @@ // import * as fs from 'fs' // import * as fsp from 'fs/promises' import { faker } from '@faker-js/faker' -// import { apiUrl } from './conf' + // const archiver = require('archiver') // // // -// declare global { -// interface Window { -// windowId?: string -// } -// } +declare global { + interface Window { + windowId?: string + } +} // // const settingsApiUrl = `${apiUrl}/settings` // process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' // lgtm[js/disabling-certificate-validation] @@ -27,7 +27,7 @@ import { faker } from '@faker-js/faker' // 'agreements': null // } // -export class Common { +// export class Common { // static mockSettingsResponse(): RequestMock { // return RequestMock() // .onRequestTo(settingsApiUrl) @@ -152,7 +152,7 @@ export class Common { * Generate word by number of symbols * @param number The number of symbols */ - static generateWord(number: number): string { + function generateWord(number: number): string { return faker.word.sample({ length: number }) } @@ -160,7 +160,7 @@ export class Common { * Generate sentence by number of words * @param number The number of words */ - static generateSentence(number: number): string { + function generateSentence(number: number): string { return faker.lorem.sentence( number) } @@ -168,25 +168,25 @@ export class Common { * Generate sentence by number of characters * @param number The number of characters */ - static generateAlpanumeric(number: number): string { + function generateAlpanumeric(number: number): string { return faker.string.alphanumeric(number) } - // + // /** // * Return api endpoint with disabled certificate validation // */ - // static getEndpoint(): string { + // static function getEndpoint(): string { // return apiUrl // } // // /** // * Return windowId // */ - // static getWindowId(): Promise { + // static function getWindowId(): Promise { // return t.eval(() => window.windowId) // } - // + // /** // * Check opened URL // * @param expectedUrl Expected link that is compared with actual @@ -195,7 +195,7 @@ export class Common { // const getPageUrl = await this.getPageUrl() // await t.expect(getPageUrl).eql(expectedUrl, 'Opened URL is not correct') // } - // + // /** // * Check opened URL contains text // * @param expectedText Expected link that is compared with actual @@ -305,4 +305,5 @@ export class Common { // isLinux: process.platform === 'linux' // } // } -} + + diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 814634ce55..97f412209d 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -292,9 +292,10 @@ export class DatabaseHelper extends BasePage{ // Accept License terms async acceptLicenseTerms(page: Page, apiUrl: string ): Promise { - // await this.myRedisDatabasePage.page.viewportSize(); // (if needed to maximize window) + // await this.myRedisDatabasePage.page.viewportSize(); + const winId = await this.getWindowId() await this.userAgreementDialog.acceptLicenseTerms() - await updateControlNumber(48.2, apiUrl) + await updateControlNumber(48.2, apiUrl, winId) // Open default databases list tab if RDI opened if (await this.rdiInstancesListPage.elementExistsLocator(this.rdiInstancesListPage.addRdiInstanceButton)) { await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) diff --git a/tests/playwright/helpers/electron/insights.ts b/tests/playwright/helpers/electron/insights.ts index 9382d96e35..af14e69393 100755 --- a/tests/playwright/helpers/electron/insights.ts +++ b/tests/playwright/helpers/electron/insights.ts @@ -33,10 +33,10 @@ export async function modifyFeaturesConfigJson(filePath: string): Promise * @param controlNumber Control number to update * @param page Playwright page instance */ -export async function updateControlNumber(controlNumber: number, apiUrl: string): Promise { - await syncFeaturesApi(apiUrl) +export async function updateControlNumber(controlNumber: number, apiUrl: string, xWindowId:string): Promise { + await syncFeaturesApi(apiUrl, xWindowId) await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }) - await syncFeaturesApi(apiUrl) + await syncFeaturesApi(apiUrl, xWindowId) } diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 9c06fcab6a..34e1a69264 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -1,5 +1,11 @@ import {Locator, Page, expect} from '@playwright/test' +declare global { + interface Window { + windowId?: string + } +} + export class BasePage { protected page: Page @@ -63,4 +69,9 @@ export class BasePage { async waitForLocatorNotVisible(locator: Locator, timeout = 6000) { await expect(locator).not.toBeVisible({ timeout }) } + + async getWindowId():Promise { + return this.page.evaluate(() => window.windowId) + + } } diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 77c1ca11cc..e4a3cb383d 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -114,8 +114,10 @@ export default defineConfig({ use: { baseURL: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', // baseURL: '/usr/bin/redisinsight', - apiUrl: process.env.API_URL || 'https://localhost:5540/api', - headless: false + // apiUrl: process.env.API_URL || 'https://localhost:5540/api', + apiUrl: 'https://localhost:5530/api', + headless: false, + }, }, diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index f7a283ae2a..50f9580d35 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -7,6 +7,10 @@ import {RedisOverviewPage} from '../helpers/constants' import {RdiInstancesListPage} from '../pageObjects/rdi-instances-list-page' import {DatabaseHelper} from "../helpers/database"; +import * as path from "node:path"; +import * as dotenv from 'dotenv'; + +dotenv.config({ path: path.resolve(__dirname, "..",'.desktop.env') }) let keyName: string let browserPage: BrowserPage From 3b56db4f63b573049c7a6c03b91792377317bc72 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 09:10:19 +0200 Subject: [PATCH 048/128] small additions --- tests/e2e/.desktop.env | 2 +- tests/playwright/fixtures/simple-slectron.ts | 30 ++----------------- tests/playwright/helpers/api/api-databases.ts | 8 +++-- tests/playwright/helpers/database.ts | 10 ++++--- tests/playwright/helpers/electron/insights.ts | 5 ++-- tests/playwright/pageObjects/index.ts | 1 + .../playwright/tests/example.electron.spec.ts | 6 ++-- 7 files changed, 20 insertions(+), 42 deletions(-) diff --git a/tests/e2e/.desktop.env b/tests/e2e/.desktop.env index ba7c8f0112..52e686c61d 100644 --- a/tests/e2e/.desktop.env +++ b/tests/e2e/.desktop.env @@ -1,4 +1,4 @@ -COMMON_URL=https://localhost:5530 +COMMON_URL=/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/resources/app.asar/dist/renderer/index.html API_URL=https://localhost:5530/api OSS_SENTINEL_PASSWORD=password RI_APP_FOLDER_NAME=.redis-insight-stage diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index 70fbeaffa7..92a83cbd08 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -80,20 +80,6 @@ export const test = base.extend win.close())); - // await electronApp.close(); - // - // console.log('🔄 Restarting RedisInsight...'); - // electronApp = await launchElectronApp(workerState.baseUrl); - // workerState.electronApp = electronApp; - // - // windows = await waitForWindows(electronApp); - // console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); - } - await use(electronApp); }, @@ -107,26 +93,14 @@ export const test = base.extend win.close())); - // await electronApp.close(); - // console.log('🔄 Restarting RedisInsight...'); - // electronApp = await launchElectronApp(workerState.baseUrl); - // workerState.electronApp = electronApp; - // windows = await waitForWindows(electronApp); - // console.log(`🔍 Rechecking for windows... Found ${windows.length} window(s).`); + let titles = (await Promise.all(windows.map(async page => ({ page, title: await page.title() })))).find(entry => entry.title)?.page || null; } - // if (windows.length === 2) { - // console.log('⚠️ Detected two windows, closing all and stopping test.'); - // await Promise.all(windows.map(win => win.close())); - // await electronApp.close() - // - // return; - // } + const window = windows[0]; await window.waitForLoadState('domcontentloaded'); diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index ec0e159f1b..63e0c81e87 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -13,7 +13,7 @@ export class DatabaseAPIRequests { this.apiClient = new HttpClient(apiUrl).getClient() } - async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false): Promise { + async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false, xWindowsId: string): Promise { const uniqueId = faker.string.alphanumeric({ length: 10 }) const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) const requestBody: any = { @@ -48,7 +48,11 @@ export class DatabaseAPIRequests { } } - const response = await this.apiClient.post(ResourcePath.Databases, requestBody) + const response = await this.apiClient.post(ResourcePath.Databases, requestBody, + {headers:{ + 'X-Window-Id': xWindowsId + } + }) if (response.status !== 201) throw new Error(`Database creation failed for ${databaseParameters.databaseName}`) } diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 97f412209d..52dad0b5cd 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -14,9 +14,10 @@ import { MyRedisDatabasePage, BrowserPage, AutoDiscoverREDatabases, - AddRedisDatabaseDialog + AddRedisDatabaseDialog, + BasePage } from '../pageObjects' -import {BasePage} from "../pageObjects/base-page"; + export class DatabaseHelper extends BasePage{ @@ -187,10 +188,11 @@ export class DatabaseHelper extends BasePage{ async acceptLicenseTermsAndAddDatabaseApi( databaseParameters: AddNewDatabaseParameters, page: Page, - apiUrl: string + apiUrl: string, + xWindowId: string ): Promise { await this.acceptLicenseTerms(page,apiUrl) - await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters) + await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters, xWindowId) // Reload Page to see the new added database through api await this.myRedisDatabasePage.reloadPage() // Connect to DB diff --git a/tests/playwright/helpers/electron/insights.ts b/tests/playwright/helpers/electron/insights.ts index af14e69393..8f9ac39cfa 100755 --- a/tests/playwright/helpers/electron/insights.ts +++ b/tests/playwright/helpers/electron/insights.ts @@ -1,5 +1,4 @@ import * as fs from 'fs-extra' -import { Page } from '@playwright/test' import * as path from 'path' import { syncFeaturesApi } from '../api/api-info' import { DatabaseScripts, DbTableParameters } from './database-scripts' @@ -43,10 +42,10 @@ export async function updateControlNumber(controlNumber: number, apiUrl: string /** * Refresh test data for features sync */ -export async function refreshFeaturesTestData(apiUrl: string): Promise { +export async function refreshFeaturesTestData(apiUrl: string, xWindowId:string): Promise { const defaultConfigPath = path.join('.', 'test-data', 'features-configs', 'insights-default-remote.json') await modifyFeaturesConfigJson(defaultConfigPath) await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams) - await syncFeaturesApi(apiUrl) + await syncFeaturesApi(apiUrl, xWindowId) } diff --git a/tests/playwright/pageObjects/index.ts b/tests/playwright/pageObjects/index.ts index c005179146..f9d789d45f 100644 --- a/tests/playwright/pageObjects/index.ts +++ b/tests/playwright/pageObjects/index.ts @@ -8,3 +8,4 @@ export * from './browser-page' export * from './my-redis-databases-page' export * from './rdi-instances-list-page' export * from './auto-discover-redis-enterprise-databases' +export * from './base-page' diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 50f9580d35..86f38b51ad 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -18,11 +18,9 @@ let databaseHelper: DatabaseHelper let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { - - - databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) - await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) + const xWindowId = await electronPage.evaluate(()=>{()=>{window.windowId}}) + await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl,xWindowId) await electronPage.getByText('Add Redis').click() // keyName = Common.generateAlpanumeric(10) // browserPage = new BrowserPage(basePage) From fc9e9b6c60b7faa120b83965d29ffc3487aad347 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 10:45:03 +0200 Subject: [PATCH 049/128] wip --- tests/playwright/fixtures/simple-slectron.ts | 55 ++++++++++++++----- .../playwright/tests/example.electron.spec.ts | 5 +- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index 92a83cbd08..f03055fe68 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -3,6 +3,7 @@ import { _electron as electron } from 'playwright'; import { ossStandaloneConfig } from '../helpers/conf'; import {updateControlNumber} from "../helpers/electron/insights"; import {DatabaseHelper} from "../helpers/database"; +import log from "node-color-log"; // Define shared state for worker scope @@ -33,6 +34,40 @@ async function launchElectronApp(baseUrl: string): Promise return electronApp; } +async function waitForWindowWithTitle( + electronApp: ElectronApplication, + + maxWaitTime = 5000, + interval = 200 +): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < maxWaitTime) { + const windows = await electronApp.windows(); + let title: string; + for (const window of windows) { + let title: string; + try{ + title = await window.title(); + }catch(e){ + log.info("❌ Window title not found") + } + + if (title) { + log.info(`✅ Found window with title: "${title}"`); + return window; + } + } + log.info(`🔍 Checking for window with title "${title}"...`); + await new Promise((resolve) => setTimeout(resolve, interval)); + } + + log.error(`❌ Window not found within ${maxWaitTime / 1000}s!`); + throw Error("❌ Window not found ") + return; +} + + async function waitForWindows(electronApp: ElectronApplication, maxWaitTime = 60000, interval = 2000) { let windows = []; let elapsedTime = 0; @@ -45,6 +80,7 @@ async function waitForWindows(electronApp: ElectronApplication, maxWaitTime = 60 return windows; } + export const test = base.extend({ workerState: [ async ({}, use, testInfo) => { @@ -84,27 +120,16 @@ export const test = base.extend { - let windows = await waitForWindows(electronApp); + let window = await waitForWindowWithTitle(electronApp); - if (windows.length === 0) { - console.error('❌ No windows detected! Stopping test.'); + if (!window) { + console.error('❌ No matching window detected! Stopping test.'); await electronApp.close(); return; } - if (windows.length === 2) { - console.log('⚠️ Detected two windows, closing all and restarting...'); - - - let titles = (await Promise.all(windows.map(async page => ({ - page, - title: await page.title() })))).find(entry => entry.title)?.page || null; - - } - - const window = windows[0]; await window.waitForLoadState('domcontentloaded'); - console.log(`🖥️ Window Title: ${await window.title()}`); + log.info(`🖥️ Window Title: ${await window.title()}`); await use(window); }, }); diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 86f38b51ad..e8fc3c9386 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -10,6 +10,7 @@ import {DatabaseHelper} from "../helpers/database"; import * as path from "node:path"; import * as dotenv from 'dotenv'; + dotenv.config({ path: path.resolve(__dirname, "..",'.desktop.env') }) let keyName: string @@ -17,9 +18,9 @@ let browserPage: BrowserPage let databaseHelper: DatabaseHelper let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { - + browserPage = new BrowserPage(electronPage) databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) - const xWindowId = await electronPage.evaluate(()=>{()=>{window.windowId}}) + const xWindowId = await browserPage.getWindowId() await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl,xWindowId) await electronPage.getByText('Add Redis').click() // keyName = Common.generateAlpanumeric(10) From 4b9041ae5611603f178ab3e8ea41c8e45991fd84 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 11:34:11 +0200 Subject: [PATCH 050/128] small typo --- tests/playwright/helpers/common.ts | 11 ++++++----- tests/playwright/tests/example.web.spec.ts | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/playwright/helpers/common.ts b/tests/playwright/helpers/common.ts index 8d31ad72f0..17436e100b 100644 --- a/tests/playwright/helpers/common.ts +++ b/tests/playwright/helpers/common.ts @@ -27,7 +27,7 @@ declare global { // 'agreements': null // } // -// export class Common { +export class Common { // static mockSettingsResponse(): RequestMock { // return RequestMock() // .onRequestTo(settingsApiUrl) @@ -152,7 +152,7 @@ declare global { * Generate word by number of symbols * @param number The number of symbols */ - function generateWord(number: number): string { + static generateWord(number: number): string { return faker.word.sample({ length: number }) } @@ -160,7 +160,7 @@ declare global { * Generate sentence by number of words * @param number The number of words */ - function generateSentence(number: number): string { + static generateSentence(number: number): string { return faker.lorem.sentence( number) } @@ -168,7 +168,7 @@ declare global { * Generate sentence by number of characters * @param number The number of characters */ - function generateAlpanumeric(number: number): string { + static generateAlphanumeric(number: number): string { return faker.string.alphanumeric(number) } @@ -304,6 +304,7 @@ declare global { // isMac: process.platform === 'darwin', // isLinux: process.platform === 'linux' // } - // } + } + diff --git a/tests/playwright/tests/example.web.spec.ts b/tests/playwright/tests/example.web.spec.ts index b43c025e51..b1300c3076 100644 --- a/tests/playwright/tests/example.web.spec.ts +++ b/tests/playwright/tests/example.web.spec.ts @@ -9,7 +9,7 @@ let browserPage: BrowserPage test.beforeEach(async ({basePage}) => { console.log('WE ARE IN THE BEFORE STEP') - keyName = Common.generateAlpanumeric(10) + keyName = Common.generateAlphanumeric(10) browserPage = new BrowserPage(basePage) }) From 8d04d2e118ae1353163f1f165020e516faaeb4ee Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 12:53:07 +0200 Subject: [PATCH 051/128] DB Created --- tests/playwright/helpers/api/api-databases.ts | 6 ++++-- tests/playwright/helpers/database.ts | 11 +++++++++-- tests/playwright/tests/example.electron.spec.ts | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 63e0c81e87..3138813003 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -4,6 +4,7 @@ import { HttpClient } from './http-client' import { AddNewDatabaseParameters, databaseParameters } from '../../types' import { ResourcePath } from '../constants' import { asyncFilter, doAsyncStuff } from '../async-helper' +import {Page} from "@playwright/test"; export class DatabaseAPIRequests { @@ -13,7 +14,8 @@ export class DatabaseAPIRequests { this.apiClient = new HttpClient(apiUrl).getClient() } - async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false, xWindowsId: string): Promise { + async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false) : Promise { + const uniqueId = faker.string.alphanumeric({ length: 10 }) const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) const requestBody: any = { @@ -50,7 +52,7 @@ export class DatabaseAPIRequests { const response = await this.apiClient.post(ResourcePath.Databases, requestBody, {headers:{ - 'X-Window-Id': xWindowsId + 'X-Window-Id': databaseParameters.xWindowsId } }) if (response.status !== 201) throw new Error(`Database creation failed for ${databaseParameters.databaseName}`) diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index 52dad0b5cd..bb1282a751 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -24,6 +24,7 @@ export class DatabaseHelper extends BasePage{ private myRedisDatabasePage: MyRedisDatabasePage private addRedisDataBaseDialog: AddRedisDatabaseDialog + // const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() private autoDiscoverREDatabases: AutoDiscoverREDatabases private browserPage: BrowserPage @@ -189,10 +190,16 @@ export class DatabaseHelper extends BasePage{ databaseParameters: AddNewDatabaseParameters, page: Page, apiUrl: string, - xWindowId: string ): Promise { + await this.acceptLicenseTerms(page,apiUrl) - await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters, xWindowId) + const winID = await this.getWindowId() + const updatedDatabaseParams = { + ...databaseParameters, + xWindowsId: winID + }; + await this.databaseAPIRequests.addNewStandaloneDatabaseApi(updatedDatabaseParams) + // await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters) // Reload Page to see the new added database through api await this.myRedisDatabasePage.reloadPage() // Connect to DB diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index e8fc3c9386..b5649d5b1a 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -20,8 +20,8 @@ let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { browserPage = new BrowserPage(electronPage) databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) - const xWindowId = await browserPage.getWindowId() - await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl,xWindowId) + + await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) await electronPage.getByText('Add Redis').click() // keyName = Common.generateAlpanumeric(10) // browserPage = new BrowserPage(basePage) From f68bb7d005e67acb3426d3fb9d7f3ebe9f68d472 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 14:34:54 +0200 Subject: [PATCH 052/128] Changes in --- tests/playwright/fixtures/simple-slectron.ts | 2 - tests/playwright/helpers/api/api-databases.ts | 25 +++++++----- tests/playwright/helpers/api/api-keys.ts | 18 ++++----- tests/playwright/helpers/database.ts | 8 +--- tests/playwright/pageObjects/browser-page.ts | 2 +- .../playwright/tests/example.electron.spec.ts | 38 +++++++++---------- 6 files changed, 44 insertions(+), 49 deletions(-) diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index f03055fe68..a83f1a9f22 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -1,8 +1,6 @@ import { test as base, ElectronApplication, Page } from '@playwright/test'; import { _electron as electron } from 'playwright'; import { ossStandaloneConfig } from '../helpers/conf'; -import {updateControlNumber} from "../helpers/electron/insights"; -import {DatabaseHelper} from "../helpers/database"; import log from "node-color-log"; diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 3138813003..59fea53393 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -14,7 +14,7 @@ export class DatabaseAPIRequests { this.apiClient = new HttpClient(apiUrl).getClient() } - async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, isCloud = false) : Promise { + async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, xWindowsId:string ,isCloud = false) : Promise { const uniqueId = faker.string.alphanumeric({ length: 10 }) const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) @@ -50,24 +50,29 @@ export class DatabaseAPIRequests { } } - const response = await this.apiClient.post(ResourcePath.Databases, requestBody, - {headers:{ - 'X-Window-Id': databaseParameters.xWindowsId + const response = await this.apiClient.post(ResourcePath.Databases, requestBody,{ + headers:{ + 'X-Window-Id': xWindowsId } }) if (response.status !== 201) throw new Error(`Database creation failed for ${databaseParameters.databaseName}`) } - async getAllDatabases(): Promise { - const response = await this.apiClient.get(ResourcePath.Databases) + async getAllDatabases(xWindowsId:string): Promise { + + const response = await this.apiClient.get(ResourcePath.Databases,{ + headers:{ + 'X-Window-Id': xWindowsId + } + }) if (response.status !== 200) throw new Error('Failed to retrieve databases') return response.data } - async getDatabaseIdByName(databaseName?: string): Promise { + async getDatabaseIdByName(databaseName?: string, xWindowsId: string): Promise { if (!databaseName) throw new Error('Error: Missing databaseName') - const allDatabases = await this.getAllDatabases() + const allDatabases = await this.getAllDatabases(xWindowsId) const filteredDb = await asyncFilter(allDatabases, async (item: databaseParameters) => { await doAsyncStuff() return item.name === databaseName @@ -77,8 +82,8 @@ export class DatabaseAPIRequests { return filteredDb[0].id } - async deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName) + async deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, xWindowsId: string): Promise { + const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName, xWindowsId) if (!databaseId) throw new Error('Error: Missing databaseId') const requestBody = { ids: [databaseId] } diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index 0ca4347dad..f6fd7512a8 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -7,11 +7,6 @@ import { SetKeyParameters, StreamKeyParameters} from '../../types' - - - - - const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' export class APIKeyRequests { @@ -63,23 +58,26 @@ export class APIKeyRequests { if (response.status !== 201) throw new Error('The creation of new Set key request failed') } - async searchKeyByNameApi(keyName: string, databaseName: string): Promise { + async searchKeyByNameApi(keyName: string, databaseName: string, xWindowsId: string): Promise { const requestBody = { cursor: '0', match: keyName } - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName, xWindowsId) const response = await this.apiClient.post(bufferPathMask.replace('databaseId', databaseId), requestBody) if (response.status !== 200) throw new Error('Getting key request failed') return response.data[0].keys } - async deleteKeyByNameApi(keyName: string, databaseName: string): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) + async deleteKeyByNameApi(keyName: string, databaseName: string, xWindowsId: string): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName, xWindowsId) const doesKeyExist = await this.searchKeyByNameApi(keyName, databaseName) if (doesKeyExist.length > 0) { const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } - const response = await this.apiClient.delete(bufferPathMask.replace('databaseId', databaseId), { data: requestBody }) + const response = await this.apiClient.delete(bufferPathMask.replace('databaseId', databaseId), { data: requestBody, + headers:{ + 'X-Window-Id': xWindowsId + }}) if (response.status !== 200) throw new Error('The deletion of the key request failed') } } diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index bb1282a751..f04a190baf 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -193,13 +193,7 @@ export class DatabaseHelper extends BasePage{ ): Promise { await this.acceptLicenseTerms(page,apiUrl) - const winID = await this.getWindowId() - const updatedDatabaseParams = { - ...databaseParameters, - xWindowsId: winID - }; - await this.databaseAPIRequests.addNewStandaloneDatabaseApi(updatedDatabaseParams) - // await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters) + await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters, await this.getWindowId()) // Reload Page to see the new added database through api await this.myRedisDatabasePage.reloadPage() // Connect to DB diff --git a/tests/playwright/pageObjects/browser-page.ts b/tests/playwright/pageObjects/browser-page.ts index 921365beae..fd63157510 100755 --- a/tests/playwright/pageObjects/browser-page.ts +++ b/tests/playwright/pageObjects/browser-page.ts @@ -11,7 +11,7 @@ import { BasePage } from './base-page' export class BrowserPage extends BasePage { // private readonly bulkActions: BulkActions // private readonly treeView: TreeView - private page: Page + page: Page private toast: Toast // CSS Selectors private readonly cssSelectorGrid: Locator diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index b5649d5b1a..5ee15ddd73 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,5 +1,5 @@ import {test, expect} from '../fixtures/simple-slectron' -// import {Common} from '../helpers/common' +import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' import {UserAgreementDialog} from '../pageObjects/dialogs/user-agreement-dialog' import {updateControlNumber} from '../helpers/electron/insights' @@ -9,6 +9,7 @@ import {DatabaseHelper} from "../helpers/database"; import * as path from "node:path"; import * as dotenv from 'dotenv'; +import {APIKeyRequests} from "../helpers/api/api-keys"; dotenv.config({ path: path.resolve(__dirname, "..",'.desktop.env') }) @@ -18,37 +19,36 @@ let browserPage: BrowserPage let databaseHelper: DatabaseHelper let rdiInstancesListPage : RdiInstancesListPage test.beforeEach(async ({electronPage, workerState}) => { + + // await electronPage.getByText('Add Redis').click() browserPage = new BrowserPage(electronPage) databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) - await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) - await electronPage.getByText('Add Redis').click() - // keyName = Common.generateAlpanumeric(10) - // browserPage = new BrowserPage(basePage) - - + keyName = Common.generateAlphanumeric(5) }) test.afterEach(async ({electronApp, workerState}) => { + + const apiKeyClient = new APIKeyRequests(workerState.apiUrl) + + await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName, await browserPage.getWindowId()) await workerState.electronApp.close() - // const apiKeyClient = new APIKeyRequests(workerState.apiUrl) - // await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) }) test('basic test', async ({workerState}) => { - // await browserPage.addHashKey(keyName) - // - // // checks that the notification is displayed (should be in a different test) - // await expect(await basePage.getByText('Key has been added')).toBeVisible() - // - // - // // Check that new key is displayed in the list - // await browserPage.searchByKeyName(keyName) - // const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) - // await expect(isKeyIsDisplayedInTheList).toBe(true) + await browserPage.addHashKey(keyName) + + // checks that the notification is displayed (should be in a different test) + await expect(await browserPage.page.getByText('Key has been added')).toBeVisible() + + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) From 112d8d71d33a7bd86ce2402cfcd1e40a1bc1ba25 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 16:10:04 +0200 Subject: [PATCH 053/128] test passing --- tests/playwright/helpers/api/api-keys.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index f6fd7512a8..10e7c32827 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -64,14 +64,18 @@ export class APIKeyRequests { match: keyName } const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName, xWindowsId) - const response = await this.apiClient.post(bufferPathMask.replace('databaseId', databaseId), requestBody) + const response = await this.apiClient.post(bufferPathMask.replace('databaseId', databaseId), requestBody, { + headers:{ + 'X-Window-Id': xWindowsId + } + }) if (response.status !== 200) throw new Error('Getting key request failed') return response.data[0].keys } async deleteKeyByNameApi(keyName: string, databaseName: string, xWindowsId: string): Promise { const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName, xWindowsId) - const doesKeyExist = await this.searchKeyByNameApi(keyName, databaseName) + const doesKeyExist = await this.searchKeyByNameApi(keyName, databaseName, xWindowsId) if (doesKeyExist.length > 0) { const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } const response = await this.apiClient.delete(bufferPathMask.replace('databaseId', databaseId), { data: requestBody, From ae8a376fc97a3c7b62fe64863508ff5075c2dbd0 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 17:15:50 +0200 Subject: [PATCH 054/128] cleanup --- tests/playwright/fixtures/electron.ts | 111 ------------------ tests/playwright/fixtures/open-ri.ts | 16 +-- tests/playwright/fixtures/simple-slectron.ts | 1 - tests/playwright/helpers/api/api-databases.ts | 2 +- tests/playwright/pageObjects/base-page.ts | 2 +- .../dialogs/user-agreement-dialog.ts | 6 + .../simpleTests}/demo-todo-app.spec.ts | 0 .../tests/simpleTests/electronPw2.js | 67 +++++++++++ 8 files changed, 83 insertions(+), 122 deletions(-) delete mode 100644 tests/playwright/fixtures/electron.ts rename tests/playwright/{tests-examples => tests/simpleTests}/demo-todo-app.spec.ts (100%) create mode 100644 tests/playwright/tests/simpleTests/electronPw2.js diff --git a/tests/playwright/fixtures/electron.ts b/tests/playwright/fixtures/electron.ts deleted file mode 100644 index 90bbd02e12..0000000000 --- a/tests/playwright/fixtures/electron.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { test as base, ElectronApplication, Page} from '@playwright/test' -import { _electron as electron} from 'playwright' -import {ossStandaloneConfig} from "../helpers/conf"; -import {DatabaseAPIRequests} from "../helpers/api/api-databases"; - -type WorkerSharedState = { - apiUrl: string; - dbConfig: typeof ossStandaloneConfig; - baseUrl: string; - electronApp: ElectronApplication; -} - -type ElectronFixture = { - electronApp: ElectronApplication; - electronPage: Page; -} -// /home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage -// /home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/redisinsight -// /home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/resources/app.asar/dist/renderer/index.html' -export const test = base.extend({ - workerState: [async ({}, use, testInfo) => { - console.log(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`) - - // Initialize worker-scoped data - const workerState: WorkerSharedState = { - apiUrl: testInfo.project.use.apiUrl, - dbConfig: ossStandaloneConfig, - baseUrl: testInfo.project.use.baseURL - } - console.log(`🏠 Base URL: ${workerState.baseUrl}`) - console.log(`🌐 API URL: ${workerState.apiUrl}`) - console.log(`🗄️ Database Config: ${JSON.stringify(workerState.dbConfig)}`) - - await use(workerState) - - }, { scope: 'worker' }], - // forEachWorker: [async ({ workerState }, use) => { - // - // workerState.electronApp.firstWindow() - // // Set up the database before tests - // // const dbApi = new DatabaseAPIRequests(workerState.apiUrl) - // // await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) - // - // await use() // Run the tests - // - // - // // Cleanup after all tests in this worker - // - // // throw new Error("test worker error") - // // await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) - // // close the app - // await workerState.electronApp.close() - // }, { scope: 'worker', auto: true }], - electronApp: async ({baseURL, workerState}, use) => { - // Launch Electron App - const electronApp = await electron.launch({ - executablePath: baseURL, - args: ['index.html'] // Adjust the path to your Electron main entry file - - }) - workerState.electronApp = electronApp - // Evaluation expression in the Electron context. - const appPath = await electronApp.evaluate(async ({ app }) => - // This runs in the main Electron process, parameter here is always - // the result of the require('electron') in the main app script. - app.getAppPath() - ) - - console.log(appPath) - // Get the first window that the app opens, wait if necessary. - const window = await electronApp.firstWindow(); - // Print the title. - console.log(await window.title()) - // Capture a screenshot. - await window.screenshot({ path: 'intro.png' }); - // Direct Electron console to Node terminal. - window.on('console', console.log); - - try { - await use(electronApp) - } catch (e) { - await electronApp.close() - } - }, - - electronPage: async ({ electronApp }, use) => { - let windows = []; - let elapsedTime = 0; - const maxWaitTime = 60000; // 60 seconds - const interval = 2000; // Check every 2 seconds - - while (windows.length === 0 && elapsedTime < maxWaitTime) { - await new Promise((resolve) => setTimeout(resolve, interval)); // Wait 2s - windows = await electronApp.windows(); // Check for open windows - elapsedTime += interval; - console.log(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); - - if (windows.length > 0) { - console.log(`✅ Found ${windows.length} window(s)!`); - break; - } - } - const page = await electronApp.firstWindow() - console.log('IN MAIN WINDOW') - - await use(page) - }, -}) - -export { expect } from '@playwright/test' diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index a16b774e0d..71b1652f17 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -4,7 +4,7 @@ import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialo import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { ossStandaloneConfig } from '../helpers/conf' import { MyRedisDatabasePage } from '../pageObjects/my-redis-databases-page' - +import log from 'node-color-log' // Define shared worker object type WorkerSharedState = { @@ -27,7 +27,7 @@ export const test = base.extend< // ✅ Worker-scoped shared object workerState: [async ({}, use, testInfo) => { - console.log(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`) + log.info(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`) // Initialize worker-scoped data const workerState: WorkerSharedState = { @@ -35,9 +35,9 @@ export const test = base.extend< dbConfig: ossStandaloneConfig, baseUrl: testInfo.project.use.baseURL } - console.log(`🏠 Base URL: ${workerState.baseUrl}`) - console.log(`🌐 API URL: ${workerState.apiUrl}`) - console.log(`🗄️ Database Config: ${JSON.stringify(workerState.dbConfig)}`) + log.info(`🏠 Base URL: ${workerState.baseUrl}`) + log.info(`🌐 API URL: ${workerState.apiUrl}`) + log.info(`🗄️ Database Config: ${JSON.stringify(workerState.dbConfig)}`) await use(workerState) @@ -46,7 +46,7 @@ export const test = base.extend< // ✅ Worker-scoped setup/teardown forEachWorker: [async ({ workerState }, use) => { const ti = base.info().workerIndex - console.log(`BEFORE Starting test worker ${ti}`) + log.info(`BEFORE Starting test worker ${ti}`) // Set up the database before tests const dbApi = new DatabaseAPIRequests(workerState.apiUrl) @@ -54,7 +54,7 @@ export const test = base.extend< await use() // Run the tests // Something failing here doesn't affect test execution result - console.log(`Stopping test worker ${ti}`) + log.info(`Stopping test worker ${ti}`) // Cleanup after all tests in this worker @@ -65,7 +65,7 @@ export const test = base.extend< // ✅ Test-scoped `basePage` using worker state basePage: async ({ page, workerState }, use) => { - console.log('Fixture setup: Initializing Base Page') + log.info('Fixture setup: Initializing Base Page') // Navigate to home page const basePage = new BasePage(page) diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index a83f1a9f22..fa37c2e4c5 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -19,7 +19,6 @@ type ElectronFixture = { async function launchElectronApp(baseUrl: string): Promise { const electronApp = await electron.launch({ - userDataDir: './ELECTRON-UDATA', executablePath: baseUrl, args: ['index.html'], timeout: 60000, diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 59fea53393..58b41dbe7f 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -4,7 +4,7 @@ import { HttpClient } from './http-client' import { AddNewDatabaseParameters, databaseParameters } from '../../types' import { ResourcePath } from '../constants' import { asyncFilter, doAsyncStuff } from '../async-helper' -import {Page} from "@playwright/test"; + export class DatabaseAPIRequests { diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index 34e1a69264..ef72c1e865 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -7,7 +7,7 @@ declare global { } export class BasePage { - protected page: Page + page: Page constructor(page: Page) { this.page = page diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index 2a5c5f63bf..5bb06b1321 100644 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -2,6 +2,7 @@ import { expect, Locator, Page } from '@playwright/test' import {BasePage} from '../base-page' import { UserAgreementSelectors } from '../../selectors' + export class UserAgreementDialog extends BasePage { // Private selectors @@ -24,6 +25,11 @@ export class UserAgreementDialog extends BasePage { async acceptLicenseTerms(): Promise { + try { + await this.switchOptionEula.waitFor({timeout: 4000}) // + }catch (error) { + + } if (await this.switchOptionEula.isVisible()) { await this.recommendedSwitcher.click() diff --git a/tests/playwright/tests-examples/demo-todo-app.spec.ts b/tests/playwright/tests/simpleTests/demo-todo-app.spec.ts similarity index 100% rename from tests/playwright/tests-examples/demo-todo-app.spec.ts rename to tests/playwright/tests/simpleTests/demo-todo-app.spec.ts diff --git a/tests/playwright/tests/simpleTests/electronPw2.js b/tests/playwright/tests/simpleTests/electronPw2.js new file mode 100644 index 0000000000..fbd796c265 --- /dev/null +++ b/tests/playwright/tests/simpleTests/electronPw2.js @@ -0,0 +1,67 @@ +const { _electron: electron } = require('@playwright/test'); +/** + * test that doesn't involve anything else than playwright meant for simple demonstration + */ +(async () => { + console.log("🚀 Starting RedisInsight..."); + + const electronApp = await electron.launch({ + executablePath: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', // CAHNGE + args: ['index.html'], + // env: { DEBUG: 'pw:electron*' }, // Enable debug logs if needed + timeout: 60000, // Max wait time + }); + + + console.log("⏳ Waiting for window..."); + + + let windows = []; + let elapsedTime = 0; + const maxWaitTime = 60000; // 60 seconds + const interval = 2000; // Check every 2 seconds + + // Wait for a window to appear + while (windows.length === 0 && elapsedTime < maxWaitTime) { + await new Promise((resolve) => setTimeout(resolve, interval)); // Wait 2s + windows = await electronApp.windows(); // Check for open windows + elapsedTime += interval; + console.log(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); + + if (windows.length > 0) { + console.log(`✅ Found ${windows.length} window(s)!`); + break; + } + } + ; + + if (windows.length === 0) { + console.error("❌ No windows detected after 60s! Exiting."); + await electronApp.close(); + return; + } + + const window = windows[0]; // Pick the first window + await window.waitForLoadState('domcontentloaded'); // Ensure it's fully loaded + + // ✅ Print the window title + const title = await window.title(); + console.log(`🖥️ Window Title: ${title}`); + + + // ✅ Click an element containing specific text + const textToClick = "Add Redis Database"; // Change this to the actual text + try { + await window.getByText(textToClick).click(); + console.log(`🖱️ Clicked on "${textToClick}" successfully!`); + } catch (error) { + console.error(`❌ Could not find or click on "${textToClick}".`, error); + } + await window.reload(); + // ✅ Capture a screenshot + await window.screenshot({ path: 'screenshot.png' }); + console.log("📸 Screenshot captured!"); + + // Exit the app. + await electronApp.close(); +})(); From dbc6f1f475999ec6cf39c86e64b6ac8b26166b27 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 17:21:18 +0200 Subject: [PATCH 055/128] state messing up web test --- .../playwright/pageObjects/dialogs/user-agreement-dialog.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index 5bb06b1321..427cc8ba8f 100644 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -1,7 +1,7 @@ import { expect, Locator, Page } from '@playwright/test' import {BasePage} from '../base-page' import { UserAgreementSelectors } from '../../selectors' - +import log from 'node-color-log' export class UserAgreementDialog extends BasePage { @@ -26,9 +26,9 @@ export class UserAgreementDialog extends BasePage { async acceptLicenseTerms(): Promise { try { - await this.switchOptionEula.waitFor({timeout: 4000}) // + await this.switchOptionEula.waitFor({timeout: 3000}) // because the state isn't clear }catch (error) { - + log.info("💩 Failed on waiting user agreement dialog ") } if (await this.switchOptionEula.isVisible()) { From c0ae0be99f31ca5d3642865c7d8f3bf336bc3688 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 19:27:37 +0200 Subject: [PATCH 056/128] cleanup + docs --- tests/playwright/README.md | 25 ++++++++++++++++++- tests/playwright/fixtures/simple-slectron.ts | 4 ++- .../playwright/tests/example.electron.spec.ts | 9 ------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 015b14cfbf..51768b5548 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -48,7 +48,6 @@ yarn playwright test --ui ## Extra tooling -Inside that directory, you can run several commands: Auto generate tests with Codegen. @@ -65,3 +64,27 @@ at java.base/java.lang.Runtime.load0(Runtime.java:755) at java.base/java.lang.System.load(System.java:1970) at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) ``` +## Writing test +Currently, the flow once the run starts is provide some configuration based on playwrite.config.ts [test project](https://playwright.dev/docs/test-projects) +(project can be viewed as a way to parametrize the tests) to the [fixture](https://playwright.dev/docs/next/test-fixtures#introduction) of the test located in [fixtures folder](./fixtures). +In the folder there is base a base fixture to start the browser that is required. Depending on the required steps you can do actions even before and after hooks in the tests. +Good example is running services, browsers etc. From the fixture you can pass down test configuration or any other data required. +Once in the test the run follows any regular unit testing framework flow. + +```mermaid +sequenceDiagram + participant Config + participant Fixture + participant Test + participant Report + Config ->>Fixture: Pass the values from playwrite.config.ts
to fixture + Fixture ->>Fixture: Self Config using dotEnv + Fixture ->>Test: Setup and trigger everything necessary
for the test and maybe pass init/config if needed + Test ->>Report: Report the result, attach logs, artefacts + +``` + +https://playwright.dev/docs/locators#quick-guide + + +https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index fa37c2e4c5..6ea6616531 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -2,8 +2,10 @@ import { test as base, ElectronApplication, Page } from '@playwright/test'; import { _electron as electron } from 'playwright'; import { ossStandaloneConfig } from '../helpers/conf'; import log from "node-color-log"; +import * as dotenv from "dotenv"; +import path from "node:path"; - +dotenv.config({ path: path.resolve(__dirname, "..",'.desktop.env') }) // Define shared state for worker scope type WorkerSharedState = { apiUrl: string; diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 5ee15ddd73..b746b87c5f 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,19 +1,10 @@ import {test, expect} from '../fixtures/simple-slectron' import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' -import {UserAgreementDialog} from '../pageObjects/dialogs/user-agreement-dialog' -import {updateControlNumber} from '../helpers/electron/insights' -import {RedisOverviewPage} from '../helpers/constants' import {RdiInstancesListPage} from '../pageObjects/rdi-instances-list-page' import {DatabaseHelper} from "../helpers/database"; - -import * as path from "node:path"; -import * as dotenv from 'dotenv'; import {APIKeyRequests} from "../helpers/api/api-keys"; - -dotenv.config({ path: path.resolve(__dirname, "..",'.desktop.env') }) - let keyName: string let browserPage: BrowserPage let databaseHelper: DatabaseHelper From 93239b8f37c2cfc850e29ceabc34a5d15dbc7f83 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 19:31:49 +0200 Subject: [PATCH 057/128] cleanup --- tests/playwright/fixtures/simple-slectron.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index 6ea6616531..e6a47a2937 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -27,7 +27,7 @@ async function launchElectronApp(baseUrl: string): Promise }); // Capture Electron logs electronApp.on('console', (msg) => { - console.log(`Electron Log: ${msg.type()} - ${msg.text()}`); + log.info(`Electron Log: ${msg.type()} - ${msg.text()}`); }); return electronApp; @@ -74,7 +74,7 @@ async function waitForWindows(electronApp: ElectronApplication, maxWaitTime = 60 await new Promise((resolve) => setTimeout(resolve, interval)); windows = await electronApp.windows(); elapsedTime += interval; - console.log(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); + log.info(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); } return windows; } @@ -83,7 +83,7 @@ async function waitForWindows(electronApp: ElectronApplication, maxWaitTime = 60 export const test = base.extend({ workerState: [ async ({}, use, testInfo) => { - console.log(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`); + log.info(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`); const workerState: WorkerSharedState = { apiUrl: testInfo.project.use.apiUrl, dbConfig: ossStandaloneConfig, @@ -98,23 +98,23 @@ export const test = base.extend { - console.log('🚀 Starting RedisInsight...'); + log.info('🚀 Starting RedisInsight...'); // update control nmb // await updateControlNumber(48.2, workerState.apiUrl); let electronApp = await launchElectronApp(workerState.baseUrl); workerState.electronApp = electronApp; - console.log('⏳ Waiting for window...'); + log.info('⏳ Waiting for window...'); let windows = await waitForWindows(electronApp); if (windows.length === 0) { - console.error('❌ No windows detected after 60s! Exiting.'); + log.error('❌ No windows detected after 60s! Exiting.'); await electronApp.close(); return; } - console.log(`✅ Found ${windows.length} window(s)!`); + log.info(`✅ Found ${windows.length} window(s)!`); await use(electronApp); }, @@ -122,7 +122,7 @@ export const test = base.extend Date: Thu, 20 Mar 2025 20:01:15 +0200 Subject: [PATCH 058/128] cleanup + docks --- tests/playwright/README.md | 27 +++++++++++++++++-- tests/playwright/package.json | 1 + .../playwright/tests/example.electron.spec.ts | 3 +-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 51768b5548..88cce6f11f 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -83,8 +83,31 @@ sequenceDiagram Test ->>Report: Report the result, attach logs, artefacts ``` +Some more usefull links for playwright +https://playwright.dev/docs/locators#quick-guide +https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate + +## Running test +### Preconditions +For RedisApp for electron Appimage (path in playwright.config,js) is required or change the path to the appropriate build folder on your machine +For docker testing you need to load the docker image for your system (or run the app form your code base) +```shell + docker load -i docker-linux-alpine.amd64.tar + or + docker load -i docker-linux-alpine.arm64.tar +``` +and change the config in playwright.config,js. + +For loading Redis databases and data similar to the other project you have [local-docker-environment](./local-docker-environment) folder. +Fil the .env file similar to [the setup in conflunce ](https://redislabs.atlassian.net/wiki/spaces/DX/pages/4906319969/Mac+setup+e2e+tests) +and execute +```shell + ./local-docker-environment/create_local_environment.sh +``` +to destroy the environment use +```shell + ./local-docker-environment/destroy_local_environment.sh +``` -https://playwright.dev/docs/locators#quick-guide -https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate diff --git a/tests/playwright/package.json b/tests/playwright/package.json index bb10b7de0f..6a01094a63 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -22,6 +22,7 @@ "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", + "test:electron:allureHistoryReport": "yarn run prep:history && yarn test:electron && yarn allure generate --clean -o allure-report allure-results", "generateAndShowReports": "allure serve allure-results", "test:autogen": "playwright codegen" }, diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index b746b87c5f..16418fce71 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,14 +1,13 @@ import {test, expect} from '../fixtures/simple-slectron' import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' -import {RdiInstancesListPage} from '../pageObjects/rdi-instances-list-page' import {DatabaseHelper} from "../helpers/database"; import {APIKeyRequests} from "../helpers/api/api-keys"; let keyName: string let browserPage: BrowserPage let databaseHelper: DatabaseHelper -let rdiInstancesListPage : RdiInstancesListPage + test.beforeEach(async ({electronPage, workerState}) => { // await electronPage.getByText('Add Redis').click() From 0408dd2ccd9a18ebb66ba3257bdfd71d02796048 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 21:35:55 +0200 Subject: [PATCH 059/128] docs and small fix --- tests/playwright/README.md | 118 ++++++++++++++---- tests/playwright/fixtures/open-ri.ts | 3 +- tests/playwright/helpers/api/api-databases.ts | 6 +- tests/playwright/playwright.config.ts | 2 - .../playwright/tests/example.electron.spec.ts | 3 + 5 files changed, 100 insertions(+), 32 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 88cce6f11f..34615383c7 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -14,29 +14,16 @@ sudo yarn playwright install-deps -## Runs -Runs the end-to-end tests. -``` -yarn playwright test -``` -Runs the tests only on Desktop Chrome. -``` -yarn playwright test --project=localChromium -``` +[More info on running tests](https://playwright.dev/docs/running-tests) -Runs the tests in debug mode. -``` -yarn playwright test --debug -``` +## Extra tooling -Runs the tests in a specific file. +Auto generate tests with Codegen. -``` -yarn playwright test example -``` +```yarn playwright codegen``` Starts the interactive UI mode. This also can be set in the config @@ -44,15 +31,6 @@ Starts the interactive UI mode. This also can be set in the config yarn playwright test --ui ``` -[More info on running tests](https://playwright.dev/docs/running-tests) - - -## Extra tooling - -Auto generate tests with Codegen. - -```yarn playwright codegen``` - Allure report display needs JAVA_HOME set and to run the server JDK version 8 to 11 otherwise you get @@ -64,6 +42,9 @@ at java.base/java.lang.Runtime.load0(Runtime.java:755) at java.base/java.lang.System.load(System.java:1970) at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) ``` + +however running the report from a server or IDE also works so it is up to you + ## Writing test Currently, the flow once the run starts is provide some configuration based on playwrite.config.ts [test project](https://playwright.dev/docs/test-projects) (project can be viewed as a way to parametrize the tests) to the [fixture](https://playwright.dev/docs/next/test-fixtures#introduction) of the test located in [fixtures folder](./fixtures). @@ -99,7 +80,7 @@ For docker testing you need to load the docker image for your system (or run th and change the config in playwright.config,js. For loading Redis databases and data similar to the other project you have [local-docker-environment](./local-docker-environment) folder. -Fil the .env file similar to [the setup in conflunce ](https://redislabs.atlassian.net/wiki/spaces/DX/pages/4906319969/Mac+setup+e2e+tests) +Fil the .env file similar to [the setup in Confluence ](https://redislabs.atlassian.net/wiki/spaces/DX/pages/4906319969/Mac+setup+e2e+tests) and execute ```shell ./local-docker-environment/create_local_environment.sh @@ -111,3 +92,86 @@ to destroy the environment use + +## Runs +Runs the end-to-end tests. (All projects) +``` shell + yarn playwright test +``` + +Runs the tests only on Desktop Chrome. + +```shell + yarn test:chromium +``` + +To run Electron Tests + +```shell + yarn test:electron +``` + + +Runs the tests in framework debug mode. + +``` +yarn playwright test --project=localChromium --debug +``` + +Runs the tests in a specific file. + +``` +yarn playwright test example +``` + +### Electron testing +Tests and setup is quite similar to browser tests. In Electron case baseUrl is path on the local file system for a prebuild/installed app +or an AppImage. In my case I used an AppImage for linux so the config was +```json lines +use: { + baseURL: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', + apiUrl: 'https://localhost:5530/api', + headless: false, + + }, +``` +that section for MacOs, where I used installed app looked like +```json lines +use: { + baseURL: '/Users/tsvetantsvetkov/Applications/RedisInsight.app/Contents/MacOS/Redis Insight', + apiUrl: 'https://localhost:5530/api', + headless: false, + + }, +``` +execution wise there were no changes. Interesting project that I came across investigating starting electron issues +this based on that blogpost [Testing Electron Apps with Playwright](https://dev.to/kubeshop/testing-electron-apps-with-playwright-3f89) +the code base [is here](https://github.com/kubeshop/monokle/blob/main/tests/electronHelpers.ts). + +They are using this helper for when a new build is created, start the test automatically by finding the latest build, +setting the correct paths and running the tests. So might be useful to have something similar implemented + + +## Reports +Running +```shell + yarn test:chromium +``` +will generate html and Allure report. However, those reports are for a single run in order to have history which is more usefull +run +```shell + yarn test:allureHistoryReport +``` +or change it depending on your needs to enable the history for the report that can show you around 20 executions + +## Improvements +Since this was a POC a small portion of the code was moved to make it work however the test code is very much a spaghetti code +plus the configuration is all over the place, and the namings are quite confusing. It needs to be +- decoupled +- and simplified + +this applies for all Page Objects, Actions and Helpers. For All tests +- test scope should be reduced +- test should start in know state +- test state should be cleared +- test should be able to run in parallel diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts index 71b1652f17..55d4c651bd 100644 --- a/tests/playwright/fixtures/open-ri.ts +++ b/tests/playwright/fixtures/open-ri.ts @@ -57,8 +57,7 @@ export const test = base.extend< log.info(`Stopping test worker ${ti}`) // Cleanup after all tests in this worker - - // throw new Error("test worker error") + // Delete db await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) }, { scope: 'worker', auto: true }], diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 58b41dbe7f..bb2095ef4a 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -87,7 +87,11 @@ export class DatabaseAPIRequests { if (!databaseId) throw new Error('Error: Missing databaseId') const requestBody = { ids: [databaseId] } - const response = await this.apiClient.delete(ResourcePath.Databases, { data: requestBody }) + const response = await this.apiClient.delete(ResourcePath.Databases, { + data: requestBody, + headers:{ + 'X-Window-Id': xWindowsId + }}) if (response.status !== 200) throw new Error(`Failed to delete database ${databaseParameters.databaseName}`) } } diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index e4a3cb383d..55cb82e1e8 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -113,8 +113,6 @@ export default defineConfig({ testMatch: ['**.electron.spec.ts'], use: { baseURL: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', - // baseURL: '/usr/bin/redisinsight', - // apiUrl: process.env.API_URL || 'https://localhost:5540/api', apiUrl: 'https://localhost:5530/api', headless: false, diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 16418fce71..05180ca2dd 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -3,6 +3,7 @@ import {Common} from '../helpers/common' import {BrowserPage} from '../pageObjects/browser-page' import {DatabaseHelper} from "../helpers/database"; import {APIKeyRequests} from "../helpers/api/api-keys"; +import {DatabaseAPIRequests} from "../helpers/api/api-databases"; let keyName: string let browserPage: BrowserPage @@ -20,8 +21,10 @@ test.beforeEach(async ({electronPage, workerState}) => { test.afterEach(async ({electronApp, workerState}) => { const apiKeyClient = new APIKeyRequests(workerState.apiUrl) + const dbApi = new DatabaseAPIRequests(workerState.apiUrl) await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName, await browserPage.getWindowId()) + await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig, await browserPage.getWindowId()) await workerState.electronApp.close() }) From 761eb72f9406fcb9bb9825dfa3540de7226a79f6 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 23:02:08 +0200 Subject: [PATCH 060/128] docs and small fix --- tests/e2e/helpers/database.ts | 12 +++++++++++- tests/playwright/README.md | 9 ++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/e2e/helpers/database.ts b/tests/e2e/helpers/database.ts index a59439542c..a8ade61266 100644 --- a/tests/e2e/helpers/database.ts +++ b/tests/e2e/helpers/database.ts @@ -10,7 +10,7 @@ import { BrowserPage, AutoDiscoverREDatabases } from '../pageObjects'; -import { UserAgreementDialog } from '../pageObjects/dialogs'; +import {OnboardingCardsDialog, UserAgreementDialog} from '../pageObjects/dialogs'; import { DatabaseAPIRequests } from './api/api-database'; import { RedisOverviewPage } from './constants'; import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page'; @@ -23,6 +23,7 @@ const browserPage = new BrowserPage(); const userAgreementDialog = new UserAgreementDialog(); const databaseAPIRequests = new DatabaseAPIRequests(); const rdiInstancesListPage = new RdiInstancesListPage(); +const onboardingCardsDialog = new OnboardingCardsDialog(); export class DatabaseHelper { /** @@ -208,10 +209,19 @@ export class DatabaseHelper { ); // Reload Page to see the new added database through api await myRedisDatabasePage.reloadPage(); + // Connect to DB await myRedisDatabasePage.clickOnDBByName( databaseParameters.databaseName! ); + + + if (await Selector('span').withText('Skip tour').exists){ + await t.click(Selector('span').withText('Skip tour')) + } + + + } /** diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 34615383c7..0ac347dee5 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -90,9 +90,6 @@ to destroy the environment use ./local-docker-environment/destroy_local_environment.sh ``` - - - ## Runs Runs the end-to-end tests. (All projects) ``` shell @@ -164,6 +161,12 @@ run ``` or change it depending on your needs to enable the history for the report that can show you around 20 executions + +| Test Name | Framework | Browser | Duration | +|------------------------|------------|-----------|----------| +|Verify that user can add Hash Key|TestCafe|Chromium| 27s | + + ## Improvements Since this was a POC a small portion of the code was moved to make it work however the test code is very much a spaghetti code plus the configuration is all over the place, and the namings are quite confusing. It needs to be From 30b5825dcdc09f28148c13ccebfb6bfbdc2d3e3a Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 23:32:50 +0200 Subject: [PATCH 061/128] docs and test improvements --- tests/playwright/README.md | 16 ++++++++++------ tests/playwright/helpers/database.ts | 5 +++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 0ac347dee5..0d3b9827d4 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -161,15 +161,19 @@ run ``` or change it depending on your needs to enable the history for the report that can show you around 20 executions - -| Test Name | Framework | Browser | Duration | -|------------------------|------------|-----------|----------| -|Verify that user can add Hash Key|TestCafe|Chromium| 27s | +Some rough execution time comparison +| Test Name | Framework | Browser | Duration| +|------------------------|------------|----------|---------| +|Verify that user can add Hash Key| TestCafe | Chromium |27s| +|Verify that user can add Hash Key| PlayWright | Chromium |10s| +|Verify that user can add Hash Key| TestCafe | Eelctron |30s| +|Verify that user can add Hash Key| PlayWright | Eelctron |18s| ## Improvements -Since this was a POC a small portion of the code was moved to make it work however the test code is very much a spaghetti code -plus the configuration is all over the place, and the namings are quite confusing. It needs to be +Since this was a proof of concept, a small portion of the code was moved to make it work. +However, the test code is quite messy, the configuration is scattered, and the naming conventions are confusing. +It needs to be - decoupled - and simplified diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts index f04a190baf..571b2bd921 100644 --- a/tests/playwright/helpers/database.ts +++ b/tests/playwright/helpers/database.ts @@ -198,6 +198,11 @@ export class DatabaseHelper extends BasePage{ await this.myRedisDatabasePage.reloadPage() // Connect to DB await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) + + if(await this.isVisible("Skip tour")){ + await this.page.locator("Skip tour").click() + } + } /** From da230dfd63c121f7ba159461e832c035ca6d9f9d Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 20 Mar 2025 23:35:51 +0200 Subject: [PATCH 062/128] docs --- tests/playwright/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 0d3b9827d4..806d070f4a 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -161,7 +161,8 @@ run ``` or change it depending on your needs to enable the history for the report that can show you around 20 executions -Some rough execution time comparison +Some rough execution time comparison for the same test + | Test Name | Framework | Browser | Duration| |------------------------|------------|----------|---------| |Verify that user can add Hash Key| TestCafe | Chromium |27s| From 25bd846a67aef963388dff5f2c4933ec802f3e20 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 21 Mar 2025 08:15:18 +0200 Subject: [PATCH 063/128] upd dock --- tests/playwright/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 806d070f4a..7770c6c373 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,14 +1,19 @@ ## Install -Install Playwright browsers + +```shell + yarn install ``` -yarn playwright install + +Install Playwright browsers +```shell + yarn playwright install ``` Install Playwright operating system dependencies requires sudo / root -``` -sudo yarn playwright install-deps +```shell + sudo yarn playwright install-deps ``` From 5c2c8b64de82fdc58b3713012dee19ba311fbf28 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 21 Mar 2025 08:59:49 +0200 Subject: [PATCH 064/128] upd dock --- tests/playwright/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 7770c6c373..449aaba5a2 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -164,7 +164,8 @@ run ```shell yarn test:allureHistoryReport ``` -or change it depending on your needs to enable the history for the report that can show you around 20 executions +or change it depending on your needs to enable the history for the report that can show you around 20 executions. Additional info can be +added to this report for more information see https://allurereport.org/docs/playwright-reference/ Some rough execution time comparison for the same test From 5b49ce38aa682bd90a4bcfa402bcbbb34a8333bd Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 21 Mar 2025 09:09:03 +0200 Subject: [PATCH 065/128] upd dock --- tests/playwright/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 449aaba5a2..f3758aacdf 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -153,6 +153,8 @@ the code base [is here](https://github.com/kubeshop/monokle/blob/main/tests/elec They are using this helper for when a new build is created, start the test automatically by finding the latest build, setting the correct paths and running the tests. So might be useful to have something similar implemented +## Debug +Add DEBUG=pw:api as env variable. This will enable the framework debug mode plus will enable log interceptors for Axios client ## Reports Running From f3c2f81f205380a1c858c2dd750f5f14c6aac185 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 21 Mar 2025 09:29:25 +0200 Subject: [PATCH 066/128] formating and spelling changes --- tests/playwright/README.md | 125 ++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index f3758aacdf..5ab1941a7e 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -16,29 +16,26 @@ Install Playwright operating system dependencies requires sudo / root sudo yarn playwright install-deps ``` - - - - - [More info on running tests](https://playwright.dev/docs/running-tests) ## Extra tooling -Auto generate tests with Codegen. +Auto-generate tests with Codegen. -```yarn playwright codegen``` +```shell + yarn playwright codegen +``` -Starts the interactive UI mode. This also can be set in the config +Starts the interactive UI mode. This also can be set in the config. -``` -yarn playwright test --ui +```shell + yarn playwright test --ui ``` -Allure report display needs JAVA_HOME set -and to run the server JDK version 8 to 11 otherwise you get +Allure report display needs JAVA_HOME set +and to run the server, JDK version 8 to 11 is required. Otherwise, you get: ``` Starting web server... Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: /usr/lib/jvm/java-17-openjdk-amd64/lib/libawt_xawt.so @@ -48,14 +45,14 @@ at java.base/java.lang.System.load(System.java:1970) at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) ``` -however running the report from a server or IDE also works so it is up to you +However, running the report from a server or IDE also works, so it is up to you. ## Writing test -Currently, the flow once the run starts is provide some configuration based on playwrite.config.ts [test project](https://playwright.dev/docs/test-projects) -(project can be viewed as a way to parametrize the tests) to the [fixture](https://playwright.dev/docs/next/test-fixtures#introduction) of the test located in [fixtures folder](./fixtures). -In the folder there is base a base fixture to start the browser that is required. Depending on the required steps you can do actions even before and after hooks in the tests. -Good example is running services, browsers etc. From the fixture you can pass down test configuration or any other data required. -Once in the test the run follows any regular unit testing framework flow. +Currently, the flow once the run starts is: provide some configuration based on `playwright.config.ts` [test project](https://playwright.dev/docs/test-projects) +(project can be viewed as a way to parametrize the tests) to the [fixture](https://playwright.dev/docs/next/test-fixtures#introduction) of the test located in the [fixtures folder](./fixtures). +In the folder, there is a base fixture to start the browser that is required. Depending on the required steps, you can do actions even before and after hooks in the tests. +A good example is running services, browsers, etc. From the fixture, you can pass down test configuration or any other data required. +Once in the test, the run follows any regular unit testing framework flow. ```mermaid sequenceDiagram @@ -69,14 +66,16 @@ sequenceDiagram Test ->>Report: Report the result, attach logs, artefacts ``` -Some more usefull links for playwright +Some more useful links for Playwright: https://playwright.dev/docs/locators#quick-guide https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate ## Running test ### Preconditions -For RedisApp for electron Appimage (path in playwright.config,js) is required or change the path to the appropriate build folder on your machine -For docker testing you need to load the docker image for your system (or run the app form your code base) + +For RedisApp for Electron, AppImage (path in `playwright.config.js`) is required or change the path to the appropriate build folder on your machine. +For Docker testing, you need to load the Docker image for your system (or run the app from your code base): + ```shell docker load -i docker-linux-alpine.amd64.tar or @@ -84,19 +83,23 @@ For docker testing you need to load the docker image for your system (or run th ``` and change the config in playwright.config,js. -For loading Redis databases and data similar to the other project you have [local-docker-environment](./local-docker-environment) folder. -Fil the .env file similar to [the setup in Confluence ](https://redislabs.atlassian.net/wiki/spaces/DX/pages/4906319969/Mac+setup+e2e+tests) -and execute +For loading Redis databases and data similar to the other project, you have the [local-docker-environment](./local-docker-environment) folder. +Fill the `.env` file similar to [the setup in Confluence](https://redislabs.atlassian.net/wiki/spaces/DX/pages/4906319969/Mac+setup+e2e+tests) +and execute: + ```shell ./local-docker-environment/create_local_environment.sh ``` -to destroy the environment use + +To destroy the environment use: ```shell ./local-docker-environment/destroy_local_environment.sh ``` ## Runs -Runs the end-to-end tests. (All projects) + +Runs the end-to-end tests (all projects): + ``` shell yarn playwright test ``` @@ -107,7 +110,7 @@ Runs the tests only on Desktop Chrome. yarn test:chromium ``` -To run Electron Tests +To run Electron tests: ```shell yarn test:electron @@ -120,15 +123,17 @@ Runs the tests in framework debug mode. yarn playwright test --project=localChromium --debug ``` -Runs the tests in a specific file. +Runs the tests in a specific file: -``` -yarn playwright test example +```shell + yarn playwright test example ``` ### Electron testing -Tests and setup is quite similar to browser tests. In Electron case baseUrl is path on the local file system for a prebuild/installed app -or an AppImage. In my case I used an AppImage for linux so the config was + +Tests and setup are quite similar to browser tests. In the Electron case, `baseURL` is the path on the local file system for a prebuilt/installed app +or an AppImage. In my case, I used an AppImage for Linux, so the config was: + ```json lines use: { baseURL: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', @@ -137,7 +142,9 @@ use: { }, ``` -that section for MacOs, where I used installed app looked like + +That section for macOS, where I used an installed app, looked like: + ```json lines use: { baseURL: '/Users/tsvetantsvetkov/Applications/RedisInsight.app/Contents/MacOS/Redis Insight', @@ -146,30 +153,35 @@ use: { }, ``` -execution wise there were no changes. Interesting project that I came across investigating starting electron issues -this based on that blogpost [Testing Electron Apps with Playwright](https://dev.to/kubeshop/testing-electron-apps-with-playwright-3f89) -the code base [is here](https://github.com/kubeshop/monokle/blob/main/tests/electronHelpers.ts). +Execution-wise, there were no changes. An interesting project that I came across while investigating starting Electron issues +is based on this blog post: [Testing Electron Apps with Playwright](https://dev.to/kubeshop/testing-electron-apps-with-playwright-3f89). +The code base [is here](https://github.com/kubeshop/monokle/blob/main/tests/electronHelpers.ts). -They are using this helper for when a new build is created, start the test automatically by finding the latest build, -setting the correct paths and running the tests. So might be useful to have something similar implemented +They are using this helper for when a new build is created, starting the test automatically by finding the latest build, +setting the correct paths, and running the tests. So it might be useful to have something similar implemented. ## Debug -Add DEBUG=pw:api as env variable. This will enable the framework debug mode plus will enable log interceptors for Axios client + +Add `DEBUG=pw:api` as an environment variable. This will enable the framework debug mode plus will enable log interceptors for the Axios client. ## Reports -Running + +Running: + ```shell yarn test:chromium ``` -will generate html and Allure report. However, those reports are for a single run in order to have history which is more usefull -run + +will generate HTML and Allure reports. However, those reports are for a single run. In order to have history, which is more useful, run: + ```shell yarn test:allureHistoryReport ``` -or change it depending on your needs to enable the history for the report that can show you around 20 executions. Additional info can be -added to this report for more information see https://allurereport.org/docs/playwright-reference/ -Some rough execution time comparison for the same test +Or change it depending on your needs to enable history for the report that can show you around 20 executions. Additional info can be +added to this report. For more information, see: https://allurereport.org/docs/playwright-reference/ + +Some rough execution time comparison for the same test: | Test Name | Framework | Browser | Duration| |------------------------|------------|----------|---------| @@ -180,14 +192,15 @@ Some rough execution time comparison for the same test ## Improvements -Since this was a proof of concept, a small portion of the code was moved to make it work. -However, the test code is quite messy, the configuration is scattered, and the naming conventions are confusing. -It needs to be -- decoupled -- and simplified - -this applies for all Page Objects, Actions and Helpers. For All tests -- test scope should be reduced -- test should start in know state -- test state should be cleared -- test should be able to run in parallel + +Since this was a proof of concept, a small portion of the code was moved to make it work. +However, the test code is quite messy, the configuration is scattered, and the naming conventions are confusing. +It needs to be: +- decoupled +- simplified + +This applies to all Page Objects, Actions, and Helpers. For all tests: +- test scope should be reduced +- test should start in a known state +- test state should be cleared +- test should be able to run in parallel From 76f5e46f34cc1eaba6dc97b4d969a49c6a2c8d58 Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Fri, 21 Mar 2025 13:43:38 +0200 Subject: [PATCH 067/128] docs --- tests/playwright/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 5ab1941a7e..691d0eec41 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -204,3 +204,6 @@ This applies to all Page Objects, Actions, and Helpers. For all tests: - test should start in a known state - test state should be cleared - test should be able to run in parallel + +(Part of what can help partly with the state management bring up specific environment in the fixture and tear it down after +the test is done, group tests that use different states. this will increase execution time but might be with it in terms of stability) From bf162d02bb108a355ea3475993b8263dc45edfac Mon Sep 17 00:00:00 2001 From: ttsvtkov Date: Thu, 27 Mar 2025 16:23:42 +0200 Subject: [PATCH 068/128] changes around test data --- tests/e2e/helpers/insights.ts | 8 +- .../web/regression/browser/ttl-format.e2e.ts | 2 +- .../insights/live-recommendations.e2e.ts | 7 +- tests/e2e/web.runner.ts | 2 +- tests/playwright/README.md | 2 +- tests/playwright/helpers/conf.ts | 6 +- .../playwright/rte/openvpn/docker-compose.yml | 13 - .../rte/openvpn/openvpn-data/conf/crl.pem | 11 - .../openvpn/openvpn-data/conf/openvpn.conf | 30 - .../conf/openvpn.conf.1636357834.bak | 30 - .../rte/openvpn/openvpn-data/conf/ovpn_env.sh | 25 - .../rte/openvpn/openvpn-data/conf/pki/ca.crt | 20 - .../139D258986D24CF7F2000F4365EA7CDE.pem | 87 - .../8055804ACAE0109030FB7947F31147A9.pem | 84 - .../rte/openvpn/openvpn-data/conf/pki/crl.pem | 11 - .../rte/openvpn/openvpn-data/conf/pki/dh.pem | 8 - .../openvpn/openvpn-data/conf/pki/index.txt | 2 - .../openvpn-data/conf/pki/index.txt.attr | 1 - .../openvpn-data/conf/pki/index.txt.attr.old | 1 - .../openvpn-data/conf/pki/index.txt.old | 1 - .../conf/pki/issued/localhost.crt | 87 - .../openvpn-data/conf/pki/issued/test.crt | 84 - .../openvpn-data/conf/pki/openssl-easyrsa.cnf | 138 -- .../openvpn-data/conf/pki/private/ca.key | 30 - .../conf/pki/private/localhost.key | 28 - .../openvpn-data/conf/pki/private/test.key | 30 - .../openvpn-data/conf/pki/reqs/localhost.req | 15 - .../openvpn-data/conf/pki/reqs/test.req | 15 - .../openvpn-data/conf/pki/safessl-easyrsa.cnf | 138 -- .../rte/openvpn/openvpn-data/conf/pki/serial | 1 - .../openvpn/openvpn-data/conf/pki/serial.old | 1 - .../rte/openvpn/openvpn-data/conf/pki/ta.key | 21 - tests/playwright/rte/openvpn/test.ovpn | 109 - .../rte/oss-cluster-7-rs/Dockerfile | 19 - .../rte/oss-cluster-7-rs/cluster-rs-create.sh | 12 - .../rte/oss-cluster-7-rs/creator.Dockerfile | 9 - .../rte/oss-cluster-7-rs/redis.conf | 1881 ----------------- tests/playwright/rte/oss-cluster-7/Dockerfile | 11 - .../rte/oss-cluster-7/cluster-create.sh | 12 - .../rte/oss-cluster-7/creator.Dockerfile | 9 - tests/playwright/rte/oss-cluster-7/redis.conf | 1881 ----------------- tests/playwright/rte/oss-sentinel/Dockerfile | 14 - .../playwright/rte/oss-sentinel/entrypoint.sh | 10 - .../playwright/rte/oss-sentinel/sentinel.conf | 15 - .../rte/oss-standalone-big/Dockerfile | 10 - .../rte/oss-standalone-big/entrypoint.sh | 9 - .../rte/oss-standalone-tls/Dockerfile | 13 - .../rte/oss-standalone-tls/certs/redis.crt | 28 - .../rte/oss-standalone-tls/certs/redis.key | 52 - .../rte/oss-standalone-tls/certs/redisCA.crt | 30 - .../rte/oss-standalone-tls/certs/user.crt | 28 - .../rte/oss-standalone-tls/certs/user.key | 52 - .../rte/redis-enterprise/Dockerfile | 9 - tests/playwright/rte/redis-enterprise/db.json | 6 - .../rte/redis-enterprise/entrypoint.sh | 42 - tests/playwright/rte/ssh/keys/pub/test.pub | 1 - tests/playwright/rte/ssh/keys/pub/testp.pub | 1 - tests/playwright/rte/ssh/keys/test | 7 - tests/playwright/rte/ssh/keys/testp | 8 - 59 files changed, 16 insertions(+), 5201 deletions(-) delete mode 100644 tests/playwright/rte/openvpn/docker-compose.yml delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old delete mode 100644 tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key delete mode 100644 tests/playwright/rte/openvpn/test.ovpn delete mode 100644 tests/playwright/rte/oss-cluster-7-rs/Dockerfile delete mode 100644 tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh delete mode 100644 tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile delete mode 100644 tests/playwright/rte/oss-cluster-7-rs/redis.conf delete mode 100644 tests/playwright/rte/oss-cluster-7/Dockerfile delete mode 100644 tests/playwright/rte/oss-cluster-7/cluster-create.sh delete mode 100644 tests/playwright/rte/oss-cluster-7/creator.Dockerfile delete mode 100644 tests/playwright/rte/oss-cluster-7/redis.conf delete mode 100644 tests/playwright/rte/oss-sentinel/Dockerfile delete mode 100644 tests/playwright/rte/oss-sentinel/entrypoint.sh delete mode 100644 tests/playwright/rte/oss-sentinel/sentinel.conf delete mode 100644 tests/playwright/rte/oss-standalone-big/Dockerfile delete mode 100644 tests/playwright/rte/oss-standalone-big/entrypoint.sh delete mode 100644 tests/playwright/rte/oss-standalone-tls/Dockerfile delete mode 100644 tests/playwright/rte/oss-standalone-tls/certs/redis.crt delete mode 100644 tests/playwright/rte/oss-standalone-tls/certs/redis.key delete mode 100644 tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt delete mode 100644 tests/playwright/rte/oss-standalone-tls/certs/user.crt delete mode 100644 tests/playwright/rte/oss-standalone-tls/certs/user.key delete mode 100644 tests/playwright/rte/redis-enterprise/Dockerfile delete mode 100644 tests/playwright/rte/redis-enterprise/db.json delete mode 100644 tests/playwright/rte/redis-enterprise/entrypoint.sh delete mode 100644 tests/playwright/rte/ssh/keys/pub/test.pub delete mode 100644 tests/playwright/rte/ssh/keys/pub/testp.pub delete mode 100644 tests/playwright/rte/ssh/keys/test delete mode 100644 tests/playwright/rte/ssh/keys/testp diff --git a/tests/e2e/helpers/insights.ts b/tests/e2e/helpers/insights.ts index 24ced1b965..bb10a6930d 100644 --- a/tests/e2e/helpers/insights.ts +++ b/tests/e2e/helpers/insights.ts @@ -38,7 +38,9 @@ export async function modifyFeaturesConfigJson(filePath: string): Promise */ export async function updateControlNumber(controlNumber: number): Promise { await syncFeaturesApi(); - await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }); + if(process.env.RI_SOCKETS_CORS){ + await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }); + } await syncFeaturesApi(); await t.eval(() => location.reload()); } @@ -50,6 +52,8 @@ export async function refreshFeaturesTestData(): Promise { const defaultConfigPath = path.join('.', 'test-data', 'features-configs', 'insights-default-remote.json'); await modifyFeaturesConfigJson(defaultConfigPath); - await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams); + if(process.env.RI_SOCKETS_CORS){ + await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams); + } await syncFeaturesApi(); } diff --git a/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts b/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts index 8e91c20f0e..cfdb202637 100644 --- a/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts +++ b/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts @@ -33,7 +33,7 @@ fixture `TTL values in Keys Table` // Clear and delete database await deleteKeysViaCli(keysData); await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); - }); + }).skip; test('Verify that user can see TTL in the list of keys rounded down to the nearest unit', async t => { // Create new keys with TTL await t.click(browserPage.Cli.cliExpandButton); diff --git a/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts b/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts index 7bfc130c7a..909a382b7c 100644 --- a/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts +++ b/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts @@ -44,7 +44,8 @@ const setPasswordRecom = RecommendationIds.setPassword; fixture `Live Recommendations` .meta({ type: 'regression', rte: rte.standalone }) .page(commonUrl) - .beforeEach(async() => { + .beforeEach(async(t) => { + await t.debug() await databaseHelper.acceptLicenseTerms(); await refreshFeaturesTestData(); await modifyFeaturesConfigJson(featuresConfig); @@ -52,7 +53,7 @@ fixture `Live Recommendations` await databaseAPIRequests.addNewStandaloneDatabaseApi(ossStandaloneV6Config); await myRedisDatabasePage.reloadPage(); await myRedisDatabasePage.clickOnDBByName(ossStandaloneV6Config.databaseName); - }) + }).only .afterEach(async() => { await refreshFeaturesTestData(); // Delete database @@ -155,7 +156,7 @@ test await recommendationsActions.voteForRecommendation(redisVersionRecom, usefulVoteOption); // Verify that user can rate recommendations with one of 2 existing types at the same time await recommendationsActions.verifyVoteIsSelected(redisVersionRecom, usefulVoteOption); - }); + }).only; test('Verify that user can hide recommendations and checkbox value is saved', async t => { const commandToGetRecommendation = 'FT.INFO'; await browserPage.Cli.sendCommandInCli(commandToGetRecommendation); diff --git a/tests/e2e/web.runner.ts b/tests/e2e/web.runner.ts index 69802c3e1d..9715ac6903 100644 --- a/tests/e2e/web.runner.ts +++ b/tests/e2e/web.runner.ts @@ -11,7 +11,7 @@ import testcafe from 'testcafe'; experimentalDecorators: true } }) .src((process.env.TEST_FILES || 'tests/web/**/*.e2e.ts').split('\n')) - .browsers(['chromium --disable-search-engine-choice-screen --ignore-certificate-errors --disable-dev-shm-usage --no-sandbox']) + .browsers(['chrome --cache --allow-insecure-localhost --disable-search-engine-choice-screen --ignore-certificate-errors']) .screenshots({ path: 'report/screenshots/', takeOnFails: true, diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 691d0eec41..3fdfa48cba 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -175,7 +175,7 @@ Running: will generate HTML and Allure reports. However, those reports are for a single run. In order to have history, which is more useful, run: ```shell - yarn test:allureHistoryReport +yarn test:allureHistoryReport ``` Or change it depending on your needs to enable history for the report that can show you around 20 executions. Additional info can be diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts index a07f8fd4de..d9fa343389 100644 --- a/tests/playwright/helpers/conf.ts +++ b/tests/playwright/helpers/conf.ts @@ -156,12 +156,12 @@ export const ossStandaloneTlsConfig = { databasePassword: process.env.OSS_STANDALONE_TLS_PASSWORD, caCert: { name: `ca}-${uniqueId}`, - certificate: process.env.E2E_CA_CRT || fs.readFileSync(path.resolve(__dirname, '../rte/oss-standalone-tls/certs/redisCA.crt'), 'utf-8') + certificate: process.env.E2E_CA_CRT || fs.readFileSync(path.resolve(__dirname, '../local-docker-environment/rte/oss-standalone-tls/certs/redisCA.crt'), 'utf-8') }, clientCert: { name: `client}-${uniqueId}`, - certificate: process.env.E2E_CLIENT_CRT || fs.readFileSync(path.resolve(__dirname, '../rte/oss-standalone-tls/certs/redis.crt'), 'utf-8'), - key: process.env.E2E_CLIENT_KEY || fs.readFileSync(path.resolve(__dirname, '../rte/oss-standalone-tls/certs/redis.key'), 'utf-8') + certificate: process.env.E2E_CLIENT_CRT || fs.readFileSync(path.resolve(__dirname, '../local-docker-environment/rte/oss-standalone-tls/certs/redis.crt'), 'utf-8'), + key: process.env.E2E_CLIENT_KEY || fs.readFileSync(path.resolve(__dirname, '../local-docker-environment/rte/oss-standalone-tls/certs/redis.key'), 'utf-8') } } diff --git a/tests/playwright/rte/openvpn/docker-compose.yml b/tests/playwright/rte/openvpn/docker-compose.yml deleted file mode 100644 index f172a60226..0000000000 --- a/tests/playwright/rte/openvpn/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: "3.4" - -services: - openvpn: - cap_add: - - NET_ADMIN - image: kylemanna/openvpn - container_name: openvpn - ports: - - "1194:1194/udp" - restart: always - volumes: - - ./openvpn-data/conf:/etc/openvpn diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem deleted file mode 100644 index 6dd7b13985..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/crl.pem +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN X509 CRL----- -MIIBqDCBkQIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0Fw0yMTEx -MDgwNzQ5MTZaFw0zMTExMDYwNzQ5MTZaoE4wTDBKBgNVHSMEQzBBgBQ/AarQse++ -Argn1cHxvXoG8W+Pc6ETpBEwDzENMAsGA1UEAwwEdGVzdIIUcqHj2uS8cK9SYQiJ -moIR/7fU0PowDQYJKoZIhvcNAQELBQADggEBAD4eZq6iex+GdwiGaEGjtIcpQGJD -eQC9xUmkZSphKOcwjFyPC6/qOZ/MAgPhqzP2urgyrgeluvhFsISXmH/Di+6yVn7f -bV4c1e0CT6/H6filAelPnbkEclCv/48P6L3BN4o4S98QXzvMeF+YfpEYGyjO+/PW -vd0UH4mdtsDpk94Z2FKxeUey76EJPSvwa08dY+/CLynSGDXdGavErtFTBVoc5qcj -XdC3CI2ig3DnUUBvwiwSxiB5vzJ8Vhl3dxTag/4yYkfjmOS9EzdDRAhRfg7wsf+v -t4HmeQ2ntBkld1MtXJSKaOQa/if5+nNyb+4ktQkLC6YBd4SNpO8yYq/dMJA= ------END X509 CRL----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf b/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf deleted file mode 100644 index 1f58509e53..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf +++ /dev/null @@ -1,30 +0,0 @@ -server 192.168.255.0 255.255.255.0 -verb 3 -key /etc/openvpn/pki/private/localhost.key -ca /etc/openvpn/pki/ca.crt -cert /etc/openvpn/pki/issued/localhost.crt -dh /etc/openvpn/pki/dh.pem -tls-auth /etc/openvpn/pki/ta.key -key-direction 0 -keepalive 10 60 -persist-key -persist-tun - -proto udp -# Rely on Docker to do port mapping, internally always 1194 -port 1194 -dev tun0 -status /tmp/openvpn-status.log - -user nobody -group nogroup -comp-lzo no - -### Push Configurations Below -push "dhcp-option DNS 192.168.13.6" -push "comp-lzo no" -push "dhcp-option DOMAIN localhost" -push "route 192.168.13.0 255.255.255.0" -push "route 172.30.0.0 255.255.0.0" -push "route 172.31.0.0 255.255.0.0" -push "route 172.33.0.0 255.255.0.0" diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak b/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak deleted file mode 100644 index 3f8e34df8e..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/openvpn.conf.1636357834.bak +++ /dev/null @@ -1,30 +0,0 @@ -server 192.168.255.0 255.255.255.0 -verb 3 -key /etc/openvpn/pki/private/localhost.key -ca /etc/openvpn/pki/ca.crt -cert /etc/openvpn/pki/issued/localhost.crt -dh /etc/openvpn/pki/dh.pem -tls-auth /etc/openvpn/pki/ta.key -key-direction 0 -keepalive 10 60 -persist-key -persist-tun - -proto udp -# Rely on Docker to do port mapping, internally always 1194 -port 1194 -dev tun0 -status /tmp/openvpn-status.log - -user nobody -group nogroup -comp-lzo no - -### Route Configurations Below -route 192.168.254.0 255.255.255.0 - -### Push Configurations Below -push "block-outside-dns" -push "dhcp-option DNS 8.8.8.8" -push "dhcp-option DNS 8.8.4.4" -push "comp-lzo no" diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh b/tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh deleted file mode 100644 index 9b3dabab03..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/ovpn_env.sh +++ /dev/null @@ -1,25 +0,0 @@ -declare -x OVPN_AUTH= -declare -x OVPN_CIPHER= -declare -x OVPN_CLIENT_TO_CLIENT= -declare -x OVPN_CN=localhost -declare -x OVPN_COMP_LZO=0 -declare -x OVPN_DEFROUTE=0 -declare -x OVPN_DEVICE=tun -declare -x OVPN_DEVICEN=0 -declare -x OVPN_DISABLE_PUSH_BLOCK_DNS=1 -declare -x OVPN_DNS=1 -declare -x OVPN_DNS_SERVERS=([0]="192.168.13.6") -declare -x OVPN_ENV=/etc/openvpn/ovpn_env.sh -declare -x OVPN_EXTRA_CLIENT_CONFIG=() -declare -x OVPN_EXTRA_SERVER_CONFIG=() -declare -x OVPN_FRAGMENT= -declare -x OVPN_KEEPALIVE='10 60' -declare -x OVPN_MTU= -declare -x OVPN_NAT=1 -declare -x OVPN_PORT=1194 -declare -x OVPN_PROTO=udp -declare -x OVPN_PUSH=([0]="dhcp-option DOMAIN localhost" [1]="route 192.168.13.0 255.255.255.0" [2]="route 172.17.0.0 255.255.0.0") -declare -x OVPN_ROUTES=() -declare -x OVPN_SERVER=192.168.255.0/24 -declare -x OVPN_SERVER_URL=udp://localhost -declare -x OVPN_TLS_CIPHER= diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt deleted file mode 100644 index e43ba72148..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ca.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDNjCCAh6gAwIBAgIUcqHj2uS8cK9SYQiJmoIR/7fU0PowDQYJKoZIhvcNAQEL -BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzQ5MTBaFw0zMTExMDYwNzQ5 -MTBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQCibt8kh9lqTC0O631rPHN0kMQ4kMQ/eZ59mKhAJZ3rBchIBrQne2yTw2z+ -X1ESa3VTkW2jyJ5r7iuo+Xyc8246tfBwO3u0DJ2DeZZOYPzMg48nJNxs3ur3iXAT -r6Aiwp0gtMNC2XcW7y5OPl8l+BhSt2PsWcdEdmLJgvRPJ2x+Ea8wivuw6FO6byK7 -Mxw7/CbNMw8Eey9eSz9kWDrgetS0kOgfqtt1ZnKDZkbLy8jFl0xW488VUrefUR1g -lOje8QySjDvzT8sUR0lASyS+/J6j/3gLlSS42e4SxMz00jEus+ye56cO16Pc+vKI -Xsev8cRPiSDTZTvc7Eaq/OcKVl11AgMBAAGjgYkwgYYwHQYDVR0OBBYEFD8BqtCx -774CuCfVwfG9egbxb49zMEoGA1UdIwRDMEGAFD8BqtCx774CuCfVwfG9egbxb49z -oROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCImaghH/t9TQ+jAMBgNV -HRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAXUrDhVAX -TkbhKRBuhUGQb03RyACQKBFM/SwhrmwpQMXo7BUuqWJ27U5/TRHrfKJxDgppmwIs -qmtrT07tA7e/OyFSZtZ9p/4H+5xM9FCsmu6YMQ3ZloHHGWmibrDNK70frVgRAEAS -FyAsEgpKZCr6OJNd7v2dbvO4AniZVVvccU17cJAx177YC3fNIuRtpHkm93D3qI+1 -4SED7rktVfXUKs6RMFmqIum5WRzgiJBAtk2GVQMrAAu/xmUPS/aqzstNte4KQ+UY -2qI9v1wYM8j+BT5nsBT02K+zOsYdkG39n7QEfcecPAjOkKsaFbSf/WZcsb6oCVgl -d/Nz24kfh76SqQ== ------END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem deleted file mode 100644 index 723ee792b8..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/139D258986D24CF7F2000F4365EA7CDE.pem +++ /dev/null @@ -1,87 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 13:9d:25:89:86:d2:4c:f7:f2:00:0f:43:65:ea:7c:de - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN=test - Validity - Not Before: Nov 8 07:49:15 2021 GMT - Not After : Feb 11 07:49:15 2024 GMT - Subject: CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:b8:d5:e2:66:d3:aa:e8:f7:2f:d8:76:b6:c6:67: - 5c:09:77:df:0b:1b:59:ca:f9:a9:fe:cc:50:91:91: - a4:2a:96:55:54:8c:a9:17:25:23:8d:93:76:05:5c: - 9e:86:68:82:22:42:52:f6:7d:72:5f:85:5c:7c:61: - d2:b2:a3:a2:5b:40:05:6f:eb:be:63:75:86:29:e7: - 97:e4:d7:20:1e:b4:c4:79:76:f7:cf:1d:70:ba:b0: - 10:ef:4e:9c:dc:15:4f:ee:b9:a7:b9:3f:f1:97:dd: - 77:0b:0e:3b:0b:c2:bd:b3:87:07:a4:95:2c:78:6b: - 7c:ac:7a:e4:02:c1:a0:3e:f5:ef:3a:51:f4:b3:4a: - 48:58:d0:16:10:8d:64:ba:a0:16:88:f0:62:55:fe: - 36:7b:9d:45:9f:f8:6d:e9:2a:1c:35:57:67:8e:2f: - 55:2f:27:87:dd:ce:df:a4:f3:9b:b5:80:7b:4a:f6: - 28:74:52:2d:cf:d9:ae:34:7f:6c:1d:89:f2:fc:00: - aa:1c:fa:a0:30:22:14:19:76:65:9c:31:60:39:5d: - 0d:0a:15:80:b2:26:44:69:73:a2:0d:11:c0:b5:21: - 6f:52:cd:4a:2f:87:23:48:28:fc:8c:db:83:83:56: - 7a:a5:63:61:4c:6c:bb:3b:80:9f:ba:ad:66:63:b0: - 63:57 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - X509v3 Subject Key Identifier: - DD:B7:0D:86:6B:E4:2F:30:5F:6C:C1:A8:A8:23:66:06:36:C4:30:BC - X509v3 Authority Key Identifier: - keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 - DirName:/CN=test - serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA - - X509v3 Extended Key Usage: - TLS Web Server Authentication - X509v3 Key Usage: - Digital Signature, Key Encipherment - X509v3 Subject Alternative Name: - DNS:localhost - Signature Algorithm: sha256WithRSAEncryption - 09:97:10:6a:b8:62:e7:e8:a5:a0:45:33:d2:85:0d:ca:61:73: - 8c:85:27:1d:3d:68:8b:65:55:4b:51:d9:86:a8:89:92:52:6b: - 98:4c:4b:75:05:ed:6e:e0:63:96:ce:44:b1:47:2a:71:32:32: - 86:f2:e3:5d:68:bd:82:1c:66:23:7a:ff:9e:e4:c3:a2:cd:79: - 2c:a0:63:9e:f5:cc:e4:71:60:0d:a5:69:5e:b5:c1:cb:4e:94: - 18:c5:f9:cd:89:c3:7a:33:4d:5b:6c:ec:9d:0c:0b:fe:72:72: - 07:b6:6d:ba:2b:10:e6:6e:0b:94:b6:3e:67:1a:c1:fe:73:e0: - dd:be:4c:1d:29:2b:01:fe:3e:ec:c6:d0:c8:de:04:77:ff:6a: - 7e:81:8f:86:1b:42:70:38:d1:47:cd:b9:11:33:9c:b2:7d:fa: - b4:5e:a2:a4:cd:0c:ed:3e:b1:28:f6:3d:6f:df:ea:34:83:b2: - fc:c1:31:28:75:02:fb:64:20:06:89:a9:31:ff:7c:0a:bc:c0: - aa:11:45:a4:e0:f4:98:cc:f7:77:21:de:41:34:32:97:3b:d7: - 88:58:47:7b:fb:c1:d2:9a:dc:5f:02:3f:4c:d9:99:71:f4:7b: - c8:31:c6:31:55:93:0e:42:28:b7:cb:43:e3:21:ce:84:de:0c: - a5:e1:7b:32 ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgIQE50liYbSTPfyAA9DZep83jANBgkqhkiG9w0BAQsFADAP -MQ0wCwYDVQQDDAR0ZXN0MB4XDTIxMTEwODA3NDkxNVoXDTI0MDIxMTA3NDkxNVow -FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAuNXiZtOq6Pcv2Ha2xmdcCXffCxtZyvmp/sxQkZGkKpZVVIypFyUjjZN2 -BVyehmiCIkJS9n1yX4VcfGHSsqOiW0AFb+u+Y3WGKeeX5NcgHrTEeXb3zx1wurAQ -706c3BVP7rmnuT/xl913Cw47C8K9s4cHpJUseGt8rHrkAsGgPvXvOlH0s0pIWNAW -EI1kuqAWiPBiVf42e51Fn/ht6SocNVdnji9VLyeH3c7fpPObtYB7SvYodFItz9mu -NH9sHYny/ACqHPqgMCIUGXZlnDFgOV0NChWAsiZEaXOiDRHAtSFvUs1KL4cjSCj8 -jNuDg1Z6pWNhTGy7O4Cfuq1mY7BjVwIDAQABo4GxMIGuMAkGA1UdEwQCMAAwHQYD -VR0OBBYEFN23DYZr5C8wX2zBqKgjZgY2xDC8MEoGA1UdIwRDMEGAFD8BqtCx774C -uCfVwfG9egbxb49zoROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCIma -ghH/t9TQ+jATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwFAYDVR0R -BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAJlxBquGLn6KWgRTPS -hQ3KYXOMhScdPWiLZVVLUdmGqImSUmuYTEt1Be1u4GOWzkSxRypxMjKG8uNdaL2C -HGYjev+e5MOizXksoGOe9czkcWANpWletcHLTpQYxfnNicN6M01bbOydDAv+cnIH -tm26KxDmbguUtj5nGsH+c+DdvkwdKSsB/j7sxtDI3gR3/2p+gY+GG0JwONFHzbkR -M5yyffq0XqKkzQztPrEo9j1v3+o0g7L8wTEodQL7ZCAGiakx/3wKvMCqEUWk4PSY -zPd3Id5BNDKXO9eIWEd7+8HSmtxfAj9M2Zlx9HvIMcYxVZMOQii3y0PjIc6E3gyl -4Xsy ------END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem deleted file mode 100644 index eda326fd95..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/certs_by_serial/8055804ACAE0109030FB7947F31147A9.pem +++ /dev/null @@ -1,84 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 80:55:80:4a:ca:e0:10:90:30:fb:79:47:f3:11:47:a9 - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN=test - Validity - Not Before: Nov 8 07:51:56 2021 GMT - Not After : Feb 11 07:51:56 2024 GMT - Subject: CN=test - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:cd:ba:3a:57:9b:0f:9b:dd:5a:c3:8c:ac:f0:24: - 2e:20:8e:3b:33:6d:86:b2:cb:81:00:83:4a:4f:16: - 40:cd:b3:e5:1d:c5:7f:98:e7:4b:a0:f5:6f:f3:5b: - 1b:a2:6b:16:4d:5c:5b:fe:46:c3:58:8e:0e:13:f9: - ec:69:68:37:f6:7d:e0:7e:8b:95:0f:71:ba:89:b1: - 5d:0e:ca:7c:9b:9e:07:57:c2:4b:e3:42:96:ef:5e: - 43:ea:fe:11:f2:38:3a:b4:0c:e3:e2:4c:28:e2:07: - bb:9a:56:63:98:88:91:15:f5:27:4d:a5:d1:88:0c: - 49:48:24:8f:71:8d:7d:0e:48:1b:d9:95:a4:7b:f2: - b7:f6:68:95:0c:14:2f:19:8d:ac:c5:cd:95:ac:42: - 93:ab:6e:60:33:40:90:f6:80:4e:a8:4b:f0:0f:d4: - d6:c0:5d:f2:8f:dd:c0:41:2b:78:96:12:60:37:e7: - c5:cc:ba:7a:36:de:0a:f0:e5:c9:90:51:3d:66:a6: - d1:b9:d2:b4:d3:ad:cb:72:f9:46:45:33:65:4a:e3: - e9:95:ee:23:37:92:b0:6b:a8:95:02:06:04:6b:7e: - 44:a9:4e:3c:fd:93:5b:32:4c:c3:40:24:9e:52:14: - d1:ac:aa:c5:88:4b:88:75:51:1c:96:26:c2:d7:75: - c6:7b - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - X509v3 Subject Key Identifier: - 8E:9E:25:9F:4F:53:6D:1D:D6:2C:08:03:2C:66:C3:6F:90:16:53:96 - X509v3 Authority Key Identifier: - keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 - DirName:/CN=test - serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA - - X509v3 Extended Key Usage: - TLS Web Client Authentication - X509v3 Key Usage: - Digital Signature - Signature Algorithm: sha256WithRSAEncryption - 00:67:d2:93:e6:69:cb:fd:f6:9f:df:f6:59:20:2a:f6:0f:03: - ba:b3:da:65:de:62:23:36:a2:8d:4f:27:22:0e:3d:01:80:d9: - 59:cd:c5:f0:1a:9b:c5:e8:f4:6f:e2:c7:29:fc:37:21:2f:6f: - 9d:b8:8c:f6:6e:37:c9:b1:4a:0d:9d:e5:cd:0a:4b:01:0a:98: - 8f:46:e9:24:97:9c:ef:75:dd:a4:f7:33:7d:df:09:f3:4c:b6: - 3c:38:a7:2e:26:1d:68:f9:87:9a:ae:6c:60:d9:de:32:f1:69: - 66:97:cb:20:81:0d:b5:01:74:b5:73:8c:85:2b:5a:73:ea:cd: - e5:25:13:44:3a:24:0a:0a:72:4d:42:cc:0b:5a:c9:96:05:20: - 37:fb:1b:95:18:8d:66:ff:10:f8:3a:d8:03:6c:6c:37:6e:de: - 51:59:08:7e:d1:33:11:08:74:ed:fc:3f:4d:19:00:82:88:9f: - 95:66:a6:e9:f1:73:55:e1:7a:3f:ae:a6:e1:b7:51:df:92:28: - 19:42:1d:a5:a7:ed:b9:e4:00:ea:a7:55:e3:55:12:45:5f:f9: - e1:a5:1f:13:f1:ee:1a:31:e5:ae:9d:2e:ef:dd:d8:56:b2:7c: - f6:ba:08:41:db:13:16:31:0e:5d:41:b2:6d:98:01:e4:43:a2: - d1:34:9e:91 ------BEGIN CERTIFICATE----- -MIIDRTCCAi2gAwIBAgIRAIBVgErK4BCQMPt5R/MRR6kwDQYJKoZIhvcNAQELBQAw -DzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzUxNTZaFw0yNDAyMTEwNzUxNTZa -MA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDNujpXmw+b3VrDjKzwJC4gjjszbYayy4EAg0pPFkDNs+UdxX+Y50ug9W/zWxui -axZNXFv+RsNYjg4T+expaDf2feB+i5UPcbqJsV0OynybngdXwkvjQpbvXkPq/hHy -ODq0DOPiTCjiB7uaVmOYiJEV9SdNpdGIDElIJI9xjX0OSBvZlaR78rf2aJUMFC8Z -jazFzZWsQpOrbmAzQJD2gE6oS/AP1NbAXfKP3cBBK3iWEmA358XMuno23grw5cmQ -UT1mptG50rTTrcty+UZFM2VK4+mV7iM3krBrqJUCBgRrfkSpTjz9k1syTMNAJJ5S -FNGsqsWIS4h1URyWJsLXdcZ7AgMBAAGjgZswgZgwCQYDVR0TBAIwADAdBgNVHQ4E -FgQUjp4ln09TbR3WLAgDLGbDb5AWU5YwSgYDVR0jBEMwQYAUPwGq0LHvvgK4J9XB -8b16BvFvj3OhE6QRMA8xDTALBgNVBAMMBHRlc3SCFHKh49rkvHCvUmEIiZqCEf+3 -1ND6MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0B -AQsFAAOCAQEAAGfSk+Zpy/32n9/2WSAq9g8DurPaZd5iIzaijU8nIg49AYDZWc3F -8Bqbxej0b+LHKfw3IS9vnbiM9m43ybFKDZ3lzQpLAQqYj0bpJJec73XdpPczfd8J -80y2PDinLiYdaPmHmq5sYNneMvFpZpfLIIENtQF0tXOMhStac+rN5SUTRDokCgpy -TULMC1rJlgUgN/sblRiNZv8Q+DrYA2xsN27eUVkIftEzEQh07fw/TRkAgoiflWam -6fFzVeF6P66m4bdR35IoGUIdpaftueQA6qdV41USRV/54aUfE/HuGjHlrp0u793Y -VrJ89roIQdsTFjEOXUGybZgB5EOi0TSekQ== ------END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem deleted file mode 100644 index 6dd7b13985..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/crl.pem +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN X509 CRL----- -MIIBqDCBkQIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0Fw0yMTEx -MDgwNzQ5MTZaFw0zMTExMDYwNzQ5MTZaoE4wTDBKBgNVHSMEQzBBgBQ/AarQse++ -Argn1cHxvXoG8W+Pc6ETpBEwDzENMAsGA1UEAwwEdGVzdIIUcqHj2uS8cK9SYQiJ -moIR/7fU0PowDQYJKoZIhvcNAQELBQADggEBAD4eZq6iex+GdwiGaEGjtIcpQGJD -eQC9xUmkZSphKOcwjFyPC6/qOZ/MAgPhqzP2urgyrgeluvhFsISXmH/Di+6yVn7f -bV4c1e0CT6/H6filAelPnbkEclCv/48P6L3BN4o4S98QXzvMeF+YfpEYGyjO+/PW -vd0UH4mdtsDpk94Z2FKxeUey76EJPSvwa08dY+/CLynSGDXdGavErtFTBVoc5qcj -XdC3CI2ig3DnUUBvwiwSxiB5vzJ8Vhl3dxTag/4yYkfjmOS9EzdDRAhRfg7wsf+v -t4HmeQ2ntBkld1MtXJSKaOQa/if5+nNyb+4ktQkLC6YBd4SNpO8yYq/dMJA= ------END X509 CRL----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem deleted file mode 100644 index 3b1acc9dd4..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/dh.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEAlG3e5COo1wC8aPTfb2ilsHyfPSj8WHMDMFfZYAiJsLAW6sNaA33L -9AfgsYDgb5CoHhil47Yrons97nvdC6lwVuG61Q7S77VX0MV6b12Gu+D8VJElnoNB -yQP/z6Frfg7OKCDelIfkvqYYPqQD33S7XR2a+7vO2E/vnc7vcfHozUUPKHqFtxyt -MNYuIs74l+2HHBHEO9fKWHc4IfHEkROQbehy0y6//qiKz/WqWAkQPX6eqgf26V23 -TyOT4UBvNv7nqOEpV4WS+zg+qH5c/kkcwSD/8jJMMi1cEWvz+9w+Kh7ponzQqOU0 -LOUysoYDcsZfuE/1SftOs44jUb4UCsKC4wIBAg== ------END DH PARAMETERS----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt deleted file mode 100644 index 7a38b10be0..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt +++ /dev/null @@ -1,2 +0,0 @@ -V 240211074915Z 139D258986D24CF7F2000F4365EA7CDE unknown /CN=localhost -V 240211075156Z 8055804ACAE0109030FB7947F31147A9 unknown /CN=test diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr deleted file mode 100644 index 3a7e39e6ee..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr +++ /dev/null @@ -1 +0,0 @@ -unique_subject = no diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old deleted file mode 100644 index 3a7e39e6ee..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.attr.old +++ /dev/null @@ -1 +0,0 @@ -unique_subject = no diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old deleted file mode 100644 index 62a33d15e5..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/index.txt.old +++ /dev/null @@ -1 +0,0 @@ -V 240211074915Z 139D258986D24CF7F2000F4365EA7CDE unknown /CN=localhost diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt deleted file mode 100644 index 723ee792b8..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/localhost.crt +++ /dev/null @@ -1,87 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 13:9d:25:89:86:d2:4c:f7:f2:00:0f:43:65:ea:7c:de - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN=test - Validity - Not Before: Nov 8 07:49:15 2021 GMT - Not After : Feb 11 07:49:15 2024 GMT - Subject: CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:b8:d5:e2:66:d3:aa:e8:f7:2f:d8:76:b6:c6:67: - 5c:09:77:df:0b:1b:59:ca:f9:a9:fe:cc:50:91:91: - a4:2a:96:55:54:8c:a9:17:25:23:8d:93:76:05:5c: - 9e:86:68:82:22:42:52:f6:7d:72:5f:85:5c:7c:61: - d2:b2:a3:a2:5b:40:05:6f:eb:be:63:75:86:29:e7: - 97:e4:d7:20:1e:b4:c4:79:76:f7:cf:1d:70:ba:b0: - 10:ef:4e:9c:dc:15:4f:ee:b9:a7:b9:3f:f1:97:dd: - 77:0b:0e:3b:0b:c2:bd:b3:87:07:a4:95:2c:78:6b: - 7c:ac:7a:e4:02:c1:a0:3e:f5:ef:3a:51:f4:b3:4a: - 48:58:d0:16:10:8d:64:ba:a0:16:88:f0:62:55:fe: - 36:7b:9d:45:9f:f8:6d:e9:2a:1c:35:57:67:8e:2f: - 55:2f:27:87:dd:ce:df:a4:f3:9b:b5:80:7b:4a:f6: - 28:74:52:2d:cf:d9:ae:34:7f:6c:1d:89:f2:fc:00: - aa:1c:fa:a0:30:22:14:19:76:65:9c:31:60:39:5d: - 0d:0a:15:80:b2:26:44:69:73:a2:0d:11:c0:b5:21: - 6f:52:cd:4a:2f:87:23:48:28:fc:8c:db:83:83:56: - 7a:a5:63:61:4c:6c:bb:3b:80:9f:ba:ad:66:63:b0: - 63:57 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - X509v3 Subject Key Identifier: - DD:B7:0D:86:6B:E4:2F:30:5F:6C:C1:A8:A8:23:66:06:36:C4:30:BC - X509v3 Authority Key Identifier: - keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 - DirName:/CN=test - serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA - - X509v3 Extended Key Usage: - TLS Web Server Authentication - X509v3 Key Usage: - Digital Signature, Key Encipherment - X509v3 Subject Alternative Name: - DNS:localhost - Signature Algorithm: sha256WithRSAEncryption - 09:97:10:6a:b8:62:e7:e8:a5:a0:45:33:d2:85:0d:ca:61:73: - 8c:85:27:1d:3d:68:8b:65:55:4b:51:d9:86:a8:89:92:52:6b: - 98:4c:4b:75:05:ed:6e:e0:63:96:ce:44:b1:47:2a:71:32:32: - 86:f2:e3:5d:68:bd:82:1c:66:23:7a:ff:9e:e4:c3:a2:cd:79: - 2c:a0:63:9e:f5:cc:e4:71:60:0d:a5:69:5e:b5:c1:cb:4e:94: - 18:c5:f9:cd:89:c3:7a:33:4d:5b:6c:ec:9d:0c:0b:fe:72:72: - 07:b6:6d:ba:2b:10:e6:6e:0b:94:b6:3e:67:1a:c1:fe:73:e0: - dd:be:4c:1d:29:2b:01:fe:3e:ec:c6:d0:c8:de:04:77:ff:6a: - 7e:81:8f:86:1b:42:70:38:d1:47:cd:b9:11:33:9c:b2:7d:fa: - b4:5e:a2:a4:cd:0c:ed:3e:b1:28:f6:3d:6f:df:ea:34:83:b2: - fc:c1:31:28:75:02:fb:64:20:06:89:a9:31:ff:7c:0a:bc:c0: - aa:11:45:a4:e0:f4:98:cc:f7:77:21:de:41:34:32:97:3b:d7: - 88:58:47:7b:fb:c1:d2:9a:dc:5f:02:3f:4c:d9:99:71:f4:7b: - c8:31:c6:31:55:93:0e:42:28:b7:cb:43:e3:21:ce:84:de:0c: - a5:e1:7b:32 ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgIQE50liYbSTPfyAA9DZep83jANBgkqhkiG9w0BAQsFADAP -MQ0wCwYDVQQDDAR0ZXN0MB4XDTIxMTEwODA3NDkxNVoXDTI0MDIxMTA3NDkxNVow -FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAuNXiZtOq6Pcv2Ha2xmdcCXffCxtZyvmp/sxQkZGkKpZVVIypFyUjjZN2 -BVyehmiCIkJS9n1yX4VcfGHSsqOiW0AFb+u+Y3WGKeeX5NcgHrTEeXb3zx1wurAQ -706c3BVP7rmnuT/xl913Cw47C8K9s4cHpJUseGt8rHrkAsGgPvXvOlH0s0pIWNAW -EI1kuqAWiPBiVf42e51Fn/ht6SocNVdnji9VLyeH3c7fpPObtYB7SvYodFItz9mu -NH9sHYny/ACqHPqgMCIUGXZlnDFgOV0NChWAsiZEaXOiDRHAtSFvUs1KL4cjSCj8 -jNuDg1Z6pWNhTGy7O4Cfuq1mY7BjVwIDAQABo4GxMIGuMAkGA1UdEwQCMAAwHQYD -VR0OBBYEFN23DYZr5C8wX2zBqKgjZgY2xDC8MEoGA1UdIwRDMEGAFD8BqtCx774C -uCfVwfG9egbxb49zoROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCIma -ghH/t9TQ+jATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwFAYDVR0R -BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQAJlxBquGLn6KWgRTPS -hQ3KYXOMhScdPWiLZVVLUdmGqImSUmuYTEt1Be1u4GOWzkSxRypxMjKG8uNdaL2C -HGYjev+e5MOizXksoGOe9czkcWANpWletcHLTpQYxfnNicN6M01bbOydDAv+cnIH -tm26KxDmbguUtj5nGsH+c+DdvkwdKSsB/j7sxtDI3gR3/2p+gY+GG0JwONFHzbkR -M5yyffq0XqKkzQztPrEo9j1v3+o0g7L8wTEodQL7ZCAGiakx/3wKvMCqEUWk4PSY -zPd3Id5BNDKXO9eIWEd7+8HSmtxfAj9M2Zlx9HvIMcYxVZMOQii3y0PjIc6E3gyl -4Xsy ------END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt deleted file mode 100644 index eda326fd95..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/issued/test.crt +++ /dev/null @@ -1,84 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 80:55:80:4a:ca:e0:10:90:30:fb:79:47:f3:11:47:a9 - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN=test - Validity - Not Before: Nov 8 07:51:56 2021 GMT - Not After : Feb 11 07:51:56 2024 GMT - Subject: CN=test - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:cd:ba:3a:57:9b:0f:9b:dd:5a:c3:8c:ac:f0:24: - 2e:20:8e:3b:33:6d:86:b2:cb:81:00:83:4a:4f:16: - 40:cd:b3:e5:1d:c5:7f:98:e7:4b:a0:f5:6f:f3:5b: - 1b:a2:6b:16:4d:5c:5b:fe:46:c3:58:8e:0e:13:f9: - ec:69:68:37:f6:7d:e0:7e:8b:95:0f:71:ba:89:b1: - 5d:0e:ca:7c:9b:9e:07:57:c2:4b:e3:42:96:ef:5e: - 43:ea:fe:11:f2:38:3a:b4:0c:e3:e2:4c:28:e2:07: - bb:9a:56:63:98:88:91:15:f5:27:4d:a5:d1:88:0c: - 49:48:24:8f:71:8d:7d:0e:48:1b:d9:95:a4:7b:f2: - b7:f6:68:95:0c:14:2f:19:8d:ac:c5:cd:95:ac:42: - 93:ab:6e:60:33:40:90:f6:80:4e:a8:4b:f0:0f:d4: - d6:c0:5d:f2:8f:dd:c0:41:2b:78:96:12:60:37:e7: - c5:cc:ba:7a:36:de:0a:f0:e5:c9:90:51:3d:66:a6: - d1:b9:d2:b4:d3:ad:cb:72:f9:46:45:33:65:4a:e3: - e9:95:ee:23:37:92:b0:6b:a8:95:02:06:04:6b:7e: - 44:a9:4e:3c:fd:93:5b:32:4c:c3:40:24:9e:52:14: - d1:ac:aa:c5:88:4b:88:75:51:1c:96:26:c2:d7:75: - c6:7b - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - X509v3 Subject Key Identifier: - 8E:9E:25:9F:4F:53:6D:1D:D6:2C:08:03:2C:66:C3:6F:90:16:53:96 - X509v3 Authority Key Identifier: - keyid:3F:01:AA:D0:B1:EF:BE:02:B8:27:D5:C1:F1:BD:7A:06:F1:6F:8F:73 - DirName:/CN=test - serial:72:A1:E3:DA:E4:BC:70:AF:52:61:08:89:9A:82:11:FF:B7:D4:D0:FA - - X509v3 Extended Key Usage: - TLS Web Client Authentication - X509v3 Key Usage: - Digital Signature - Signature Algorithm: sha256WithRSAEncryption - 00:67:d2:93:e6:69:cb:fd:f6:9f:df:f6:59:20:2a:f6:0f:03: - ba:b3:da:65:de:62:23:36:a2:8d:4f:27:22:0e:3d:01:80:d9: - 59:cd:c5:f0:1a:9b:c5:e8:f4:6f:e2:c7:29:fc:37:21:2f:6f: - 9d:b8:8c:f6:6e:37:c9:b1:4a:0d:9d:e5:cd:0a:4b:01:0a:98: - 8f:46:e9:24:97:9c:ef:75:dd:a4:f7:33:7d:df:09:f3:4c:b6: - 3c:38:a7:2e:26:1d:68:f9:87:9a:ae:6c:60:d9:de:32:f1:69: - 66:97:cb:20:81:0d:b5:01:74:b5:73:8c:85:2b:5a:73:ea:cd: - e5:25:13:44:3a:24:0a:0a:72:4d:42:cc:0b:5a:c9:96:05:20: - 37:fb:1b:95:18:8d:66:ff:10:f8:3a:d8:03:6c:6c:37:6e:de: - 51:59:08:7e:d1:33:11:08:74:ed:fc:3f:4d:19:00:82:88:9f: - 95:66:a6:e9:f1:73:55:e1:7a:3f:ae:a6:e1:b7:51:df:92:28: - 19:42:1d:a5:a7:ed:b9:e4:00:ea:a7:55:e3:55:12:45:5f:f9: - e1:a5:1f:13:f1:ee:1a:31:e5:ae:9d:2e:ef:dd:d8:56:b2:7c: - f6:ba:08:41:db:13:16:31:0e:5d:41:b2:6d:98:01:e4:43:a2: - d1:34:9e:91 ------BEGIN CERTIFICATE----- -MIIDRTCCAi2gAwIBAgIRAIBVgErK4BCQMPt5R/MRR6kwDQYJKoZIhvcNAQELBQAw -DzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzUxNTZaFw0yNDAyMTEwNzUxNTZa -MA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDNujpXmw+b3VrDjKzwJC4gjjszbYayy4EAg0pPFkDNs+UdxX+Y50ug9W/zWxui -axZNXFv+RsNYjg4T+expaDf2feB+i5UPcbqJsV0OynybngdXwkvjQpbvXkPq/hHy -ODq0DOPiTCjiB7uaVmOYiJEV9SdNpdGIDElIJI9xjX0OSBvZlaR78rf2aJUMFC8Z -jazFzZWsQpOrbmAzQJD2gE6oS/AP1NbAXfKP3cBBK3iWEmA358XMuno23grw5cmQ -UT1mptG50rTTrcty+UZFM2VK4+mV7iM3krBrqJUCBgRrfkSpTjz9k1syTMNAJJ5S -FNGsqsWIS4h1URyWJsLXdcZ7AgMBAAGjgZswgZgwCQYDVR0TBAIwADAdBgNVHQ4E -FgQUjp4ln09TbR3WLAgDLGbDb5AWU5YwSgYDVR0jBEMwQYAUPwGq0LHvvgK4J9XB -8b16BvFvj3OhE6QRMA8xDTALBgNVBAMMBHRlc3SCFHKh49rkvHCvUmEIiZqCEf+3 -1ND6MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0B -AQsFAAOCAQEAAGfSk+Zpy/32n9/2WSAq9g8DurPaZd5iIzaijU8nIg49AYDZWc3F -8Bqbxej0b+LHKfw3IS9vnbiM9m43ybFKDZ3lzQpLAQqYj0bpJJec73XdpPczfd8J -80y2PDinLiYdaPmHmq5sYNneMvFpZpfLIIENtQF0tXOMhStac+rN5SUTRDokCgpy -TULMC1rJlgUgN/sblRiNZv8Q+DrYA2xsN27eUVkIftEzEQh07fw/TRkAgoiflWam -6fFzVeF6P66m4bdR35IoGUIdpaftueQA6qdV41USRV/54aUfE/HuGjHlrp0u793Y -VrJ89roIQdsTFjEOXUGybZgB5EOi0TSekQ== ------END CERTIFICATE----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf deleted file mode 100644 index 5c4fc79e7e..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/openssl-easyrsa.cnf +++ /dev/null @@ -1,138 +0,0 @@ -# For use with Easy-RSA 3.0+ and OpenSSL or LibreSSL - -#################################################################### -[ ca ] -default_ca = CA_default # The default ca section - -#################################################################### -[ CA_default ] - -dir = $ENV::EASYRSA_PKI # Where everything is kept -certs = $dir # Where the issued certs are kept -crl_dir = $dir # Where the issued crl are kept -database = $dir/index.txt # database index file. -new_certs_dir = $dir/certs_by_serial # default place for new certs. - -certificate = $dir/ca.crt # The CA certificate -serial = $dir/serial # The current serial number -crl = $dir/crl.pem # The current CRL -private_key = $dir/private/ca.key # The private key -RANDFILE = $dir/.rand # private random number file - -x509_extensions = basic_exts # The extensions to add to the cert - -# This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA -# is designed for will. In return, we get the Issuer attached to CRLs. -crl_extensions = crl_ext - -default_days = $ENV::EASYRSA_CERT_EXPIRE # how long to certify for -default_crl_days= $ENV::EASYRSA_CRL_DAYS # how long before next CRL -default_md = $ENV::EASYRSA_DIGEST # use public key default MD -preserve = no # keep passed DN ordering - -# This allows to renew certificates which have not been revoked -unique_subject = no - -# A few different ways of specifying how similar the request should look -# For type CA, the listed attributes must be the same, and the optional -# and supplied fields are just that :-) -policy = policy_anything - -# For the 'anything' policy, which defines allowed DN fields -[ policy_anything ] -countryName = optional -stateOrProvinceName = optional -localityName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -name = optional -emailAddress = optional - -#################################################################### -# Easy-RSA request handling -# We key off $DN_MODE to determine how to format the DN -[ req ] -default_bits = $ENV::EASYRSA_KEY_SIZE -default_keyfile = privkey.pem -default_md = $ENV::EASYRSA_DIGEST -distinguished_name = $ENV::EASYRSA_DN -x509_extensions = easyrsa_ca # The extensions to add to the self signed cert - -# A placeholder to handle the $EXTRA_EXTS feature: -#%EXTRA_EXTS% # Do NOT remove or change this line as $EXTRA_EXTS support requires it - -#################################################################### -# Easy-RSA DN (Subject) handling - -# Easy-RSA DN for cn_only support: -[ cn_only ] -commonName = Common Name (eg: your user, host, or server name) -commonName_max = 64 -commonName_default = $ENV::EASYRSA_REQ_CN - -# Easy-RSA DN for org support: -[ org ] -countryName = Country Name (2 letter code) -countryName_default = $ENV::EASYRSA_REQ_COUNTRY -countryName_min = 2 -countryName_max = 2 - -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default = $ENV::EASYRSA_REQ_PROVINCE - -localityName = Locality Name (eg, city) -localityName_default = $ENV::EASYRSA_REQ_CITY - -0.organizationName = Organization Name (eg, company) -0.organizationName_default = $ENV::EASYRSA_REQ_ORG - -organizationalUnitName = Organizational Unit Name (eg, section) -organizationalUnitName_default = $ENV::EASYRSA_REQ_OU - -commonName = Common Name (eg: your user, host, or server name) -commonName_max = 64 -commonName_default = $ENV::EASYRSA_REQ_CN - -emailAddress = Email Address -emailAddress_default = $ENV::EASYRSA_REQ_EMAIL -emailAddress_max = 64 - -#################################################################### -# Easy-RSA cert extension handling - -# This section is effectively unused as the main script sets extensions -# dynamically. This core section is left to support the odd usecase where -# a user calls openssl directly. -[ basic_exts ] -basicConstraints = CA:FALSE -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer:always - -# The Easy-RSA CA extensions -[ easyrsa_ca ] - -# PKIX recommendations: - -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid:always,issuer:always - -# This could be marked critical, but it's nice to support reading by any -# broken clients who attempt to do so. -basicConstraints = CA:true - -# Limit key usage to CA tasks. If you really want to use the generated pair as -# a self-signed cert, comment this out. -keyUsage = cRLSign, keyCertSign - -# nsCertType omitted by default. Let's try to let the deprecated stuff die. -# nsCertType = sslCA - -# CRL extensions. -[ crl_ext ] - -# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. - -# issuerAltName=issuer:copy -authorityKeyIdentifier=keyid:always,issuer:always - diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key deleted file mode 100644 index d47b74f89d..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/ca.key +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,9953B10D522B006610527D745ED2B3FF - -xOtU092mS9pMlkXv5EZVQ3mAAnOPh/qb/0zmHagFTys1jHKKBGnXUWr8cYGD5Dyk -NVDoEn3RjAKhNV0Hvs18m/MUDqsuqj1fNw55LWszQ0/OioOA3+CiZlRd3qUmOFIZ -2oG+5l/k1XoH2S/zho4L53vxAMcTr62R+BXO++G4C1nMFwIchjpYInCoVZZblhP/ -Iue1lnYKkFhV609F5digBdM2mNIBJi5RST5T9rm0/EkxYNcfJgq9WMI4fN6ISXFm -KgxAT9+22v90KEGTb7ehNoczOZ7YS3kYC4zMoniIZ78E5Fv0d83strIa0S6rkKL+ -hGZ6KmVrtJpisdUIQ6Vv6aD5UCJfxdC/mC64F+gs0ElrYFC4t+Rb1uA2YyNgfuue -K0uAKcXPA4mpVyHd95TP5Yf332DiifnfO7/HqnULC+YQ+4Eo0zYJEfqbSbdI1hJD -gcneKrtRMIFL0ypzD9d8mGZce5hOHeJXR+xSWweMlQADYT/lmILHwUkShnSxa9em -S8ZgQNw56yIv1Ug3aBbtxxa/qYHWv11ZZeXA5vvkvXwt4lxmsFxYg8PySpoOqbE4 -XoJ4u5VeNS3EZZ2fHuDraTAkisfJoXRq+nYnNxK5Y+v03/y3/Ywj7UOSvH5b04Mu -lMiauOstDwlMZWsH+BqN70LWbddffZ3z2t6RdQbgxWHucMA4WB9EN18ynM3IEIM/ -HfiNVM5JwyZfsXULAq1bwCbkvbuuZDzjF0itBi7nZA6H+bdETP9neHUtHHhqW+zP -lQmQt4qq0JMkDNXQUt69p213iy8lTOnWzMhfLoTcqcJxFjUjujb3DUSOfcmFx8a8 -5sYaXbiSY8PbjWeX8S/dFx94Zwyy1pWj/rWiGVjeUjvxDzdfFyKf8JSSaXltCVvw -HoKaQFycuB0cuWBo+UxHRI+sUDj0rzdNSR1MxdCJbnWmOJBOoQCZWPcodt9ttSux -OxSNTU90bUMKRyAI9FevWXR7ZGx1o+VXPG0lGAD638eHIfBfc6RLqiYAyrDLOp8y -/yRvTCJAFIvSfqPOjXjDX9OI6KhNpsQRpLeEJrTG8DPDLvjTqb+yCimryIe0GpM2 -H8OGu9bQkUzbotQ/c1wPXWRGUgk0wJG5vwNE58y9QpkxZEkToXcE9DZ/SdyXd7YU -fA1V67Li0AfnBXAS921GHWvwH8di+OEpquy91ftv6Dwq429h2TFAqlPi3QpfGhgP -QQlmv5y5Wfi9SVqOTa/lz31NyJ2CVZpMdC+8vGZg1YTisss/ARYZtPBlcozYp9Op -ubovrEVugPzmzbTB8FxRuw+6GdCO0502B1k/32bvuV7XUMXltNJR094i0d/VqGeZ -WxdGMVSYn+32ai9YZeAVvfAisBjAVmG49iNpCAkL4a5s2ONbJ+H7AhhQadDmBoxA -JA4JNeV19LgSWUxfNE+8IMpoDewa8RQnr68VALZDIZ8TkJvMet2fC2xy86cSNTsJ -MNbIzVZ8d+ZazE1Ki6jaToX9i4a/fBhftG4Ssi9TlmkpPli+/JgHA6WOY58+ZiZ/ -pFht4WjYeayos9hRDwGrXqVJ/pQph5swTZIbvOkIkCtNRiDkHW4VLq8gVI8eNcxg ------END RSA PRIVATE KEY----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key deleted file mode 100644 index 4e185149a8..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/localhost.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC41eJm06ro9y/Y -drbGZ1wJd98LG1nK+an+zFCRkaQqllVUjKkXJSONk3YFXJ6GaIIiQlL2fXJfhVx8 -YdKyo6JbQAVv675jdYYp55fk1yAetMR5dvfPHXC6sBDvTpzcFU/uuae5P/GX3XcL -DjsLwr2zhweklSx4a3yseuQCwaA+9e86UfSzSkhY0BYQjWS6oBaI8GJV/jZ7nUWf -+G3pKhw1V2eOL1UvJ4fdzt+k85u1gHtK9ih0Ui3P2a40f2wdifL8AKoc+qAwIhQZ -dmWcMWA5XQ0KFYCyJkRpc6INEcC1IW9SzUovhyNIKPyM24ODVnqlY2FMbLs7gJ+6 -rWZjsGNXAgMBAAECggEAC47EQ23E6CBcy4pQz4MzDByEw+sH6FtIN/iKqS+UNl2E -JG9PO3VFDdtEq/0opHrweDfxfBxIBJUQPW7Yf/cfddlA/cid9RYqN4CVzOduyLzA -9F9uC3Np0yKjTHN7938VqnXNeRX4g2iDwxs2sfuRZSJyZeY2C6mc+CQPEeUNoiIf -iEevqy5uk08CcuZfT6mr/AM3EOhvtLc5Vz2jlDBR5DkkEibIzkiU9cBQqgfeoZee -aYy0SvowJ+YfeF+sq/Hj3XBozBfIvYrHSLjMduI2ZBLrNgC3rGzYZqNXCoRoEB9q -9par8S7bxvr8v0fPr+IgxOmYU3btOWdObD4WV8ZJYQKBgQDc9uIQwV8VcE+3oPn+ -bretrIn6gNSBpf5uHSN37Gj1SoZSA77hADRLb6S2nrEQe94ufA8wG7MbLW6n8QuA -K8mPKS8jNlYm3UVX58mvusAdl6aX5eVoJFwDnlOAr1QXxxS4elmkkg1a+LhGMLY1 -sYcBzFuCb4V7w5+1X8COVeWxqQKBgQDWJIBCuDDOGUDqP4xDz6g2h73Lprd5SqFW -S4wx+ANdW1KAqF8HC4F2wYtWOxNmhtSmcQDXoc46nAdc0K/ztt7DEaVIpmM0JkoB -UQd3jlDSSyxR2dqIbYX8G/orPspeDvDo8hZIy2c7Q6X0ECXeRKQT1QKLtlgGWbwx -uTjlLlyM/wKBgEgpjJzCOiZLPo2O0fYI/I2Iiqs3xa7tohEeDQZOO9zX0NQaCw16 -PpngRR+q5dKnUWFG/MLOWQjoJnpMW2ApbLMHVTCfvzUEEHBr708nozRG4Y4781a2 -gSJhY43kNIdradXxhzMay6fJEI1DvU39w0SQE9aeSy6nLZJTShRx8wi5AoGALYDY -GFVXWZQYLS6PuaEYl23EsM+eURHAsrue8MdIv7+2Cky81JxnSDrcRtiKyNCBw1Rj -324silu5XMmjeeAz+bN4MbXZgq1YMGttcMQ2/Fio9EOcojzTqbywLhxLmC0ImBdJ -EPLX3soDK5d61H41d1leDx4SGldbHB3Frz1owTUCgYBTW5pDgCdGKLWumNc89wyX -F0nGycd4/8sEJWozfgsQ+FmPlvqYjCke9nHTM6TIhTW9s6LxxmzIKIKIvcDPIMDR -B2zNldkuL7+tU5LSzEXi9rTVKGhYuVEC10sbaeWMLgtOxWe08TBZrTDW+Hec1BUT -tTqkU6SGHfgXMazbyF+DhA== ------END PRIVATE KEY----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key deleted file mode 100644 index 389dd93f5d..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/private/test.key +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIppK+RhgYEmsCAggA -MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECG/rQJN9VfmpBIIEyMxNUosBZiWi -7ziO5LeP7tTeeNtTElo/BG7UgDfExEVyhfUvi/VF5yPNoGGzpIfSlUIdxs/IhkMA -qvBBgHGZUzHTrPcxPv3HFA99MQ3sOXbLxV28ilzt/IHyNb2XuP6IHqmgdOIKIdJq -0ln+WsToJLSJQy0wm56PJjcV5pnh+99wNQxnUO8N4PME+y1pkauBJ++i3y4t86Jn -hJJd2ViSnJQ6B4xe9+iaV1qxGNq/b35T7P38hUI3r5FmmX3k8OFScmpExuSJKkwy -G2PS5Q9V8jgYKCDHFqzdhRNMsfFOaeA4y6wlf8Vs8EvvFgwZuNWMZHLXrkLdsk/S -pmVSMQZB584+2y/LrUATKo72Whx3yXxNxBXLL3/vqHef2luCDQMgxVHnTEnczppA -2ABhgqXCz5/RwYiOL1ZIXrHpvL/j1UmCyZBA695hBNOLtSDwBmVeFaMZpfsQqu/h -624mbu/ifSYyXDtrd4XFISqSIEd1eAzXSQNS90UWzxQJQ13Yv57PBPnCvbUxWiuI -UhkHfgldYLvoswZ8B6Eenfw4b2EguFEksaYtrdiJ1H4F2EHP5i/ecXMFfBZ6f5+G -SDzkODUUnniIW3j6XQKRERWp5t1bSYopSWLI259ntmGHTe6zzRjCf4D2POJxA6cy -NDBXd1xxMZnjhDXO8y4vg8MIp4WhvNMvBAOS85mb2cFbL3eYZDokFe0XhTjEklai -YdLSTGa0aU2x5u6FDGulvSwBSuHknHFfk9ABOZ0bVpMr7pYWQ0M/lB+OdU+msbkx -liMVyrnsZjTfYhv/X8HQUKy087i7D6FNtcmNENkJKN7AAjnDaPiBizdMVWNSKrEa -ox+V1uWpXba0HFtl57V1c3FvuXYtpQ7vSk3BrJMtSkkWalWmCxE6df7nJeygG4tj -CskvgR75N6TOiNK6C8gKXZbirufICH47hJHzPbfDK+Wwl+tccXqL+5rcqGpqaHGw -ED3Y5QkqLuk0wCBiyyuEX4E+l/Is64dRaiahjqp2M/SA4zuk1kIhEPRGyN9W6jx4 -T3yocMdXq7N87O+gA5XW+TPZlDxZeGIJK2V1qWpuvdbBFbT/PEdXVbFhErEVs2Vp -KIxWGYxeajOruPdHBAbLtJaWiqqG84QCglbd240eG7fXse+onVHa9r0mhIgBdzOa -Keheq5Wrjf6Zk9ljnzCEl79Bg92ZTWwOmOsXAWVL0eHS9VOkuSfjIVhEP0D69fxE -dOeMB5ECy5vlnejkRnM0jtfaRuM+7ZNydKAL0z3gKjnBzYotNu8qcw/Pf/+c5qy3 -M1kZGeY/y+wdj+vHC9Tmi8dk21+ZEuZJ8Yb0kvv3lSo5UHdv8OxxQyVQdXc8M/Nx -ZiMo8lryBhNbK6AIguukcTUo7HXXLg7+M78H7VKq/syZJvBVU9RT0NU7Z+amFgh+ -YXQPnu5IUCPAxTIEU0f17GXMLLkp2rRNMLiJVGZmyD+BTkWhc7z+z4jgtR16gqUA -GBBzYI3ZlOekfNEKIemLYn3AaPAB79myaJxVsy/9KzNj2xv3r2ClAF0EWJLnhMrq -dqHqhxkqjTeDAfqJwNrxWJ7Wvp5I9uJ+a2wIyrY5gluhEcdiaw3ehB9/ykLAMvtQ -lpntxcSG4bJTbVuDW/JNRQ== ------END ENCRYPTED PRIVATE KEY----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req deleted file mode 100644 index e13ce06215..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/localhost.req +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAuNXiZtOq6Pcv2Ha2xmdcCXffCxtZyvmp/sxQkZGk -KpZVVIypFyUjjZN2BVyehmiCIkJS9n1yX4VcfGHSsqOiW0AFb+u+Y3WGKeeX5Ncg -HrTEeXb3zx1wurAQ706c3BVP7rmnuT/xl913Cw47C8K9s4cHpJUseGt8rHrkAsGg -PvXvOlH0s0pIWNAWEI1kuqAWiPBiVf42e51Fn/ht6SocNVdnji9VLyeH3c7fpPOb -tYB7SvYodFItz9muNH9sHYny/ACqHPqgMCIUGXZlnDFgOV0NChWAsiZEaXOiDRHA -tSFvUs1KL4cjSCj8jNuDg1Z6pWNhTGy7O4Cfuq1mY7BjVwIDAQABoAAwDQYJKoZI -hvcNAQELBQADggEBAJ6ELpvR6jUrCnMqg+/CDeiWXVoB72tC9EQQxprQhRHxxcE+ -MzThuE9/bdC6UwJur+272Ih72/bPRCPdRO3H8338M7RQHW8CVA3lVrLNxjxehx4U -rmiSuZqo+BsRYkz3IYjndRe+1I5Dhbi5qrNcognuBg6yGnNAqsFQRTkoeR20smxt -h1WNsAT+p9a0gRo6MCtJRvzsZX3B4N1C+n18hfHOHiiG3gMHHWR61O4BKYbgImhb -B3e6mQox4a3a5tHNgJrO0Q1LMrfAml2VMv/Gmx46f6M7GWDKRifXiP0hyY8OVvrQ -uoTvWF2Fhas7TTRNeAE+m2Bpac8i8Umuf6OeOcs= ------END CERTIFICATE REQUEST----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req deleted file mode 100644 index 93d6d6bed8..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/reqs/test.req +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICVDCCATwCAQAwDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAM26OlebD5vdWsOMrPAkLiCOOzNthrLLgQCDSk8WQM2z5R3F -f5jnS6D1b/NbG6JrFk1cW/5Gw1iODhP57GloN/Z94H6LlQ9xuomxXQ7KfJueB1fC -S+NClu9eQ+r+EfI4OrQM4+JMKOIHu5pWY5iIkRX1J02l0YgMSUgkj3GNfQ5IG9mV -pHvyt/ZolQwULxmNrMXNlaxCk6tuYDNAkPaATqhL8A/U1sBd8o/dwEEreJYSYDfn -xcy6ejbeCvDlyZBRPWam0bnStNOty3L5RkUzZUrj6ZXuIzeSsGuolQIGBGt+RKlO -PP2TWzJMw0AknlIU0ayqxYhLiHVRHJYmwtd1xnsCAwEAAaAAMA0GCSqGSIb3DQEB -CwUAA4IBAQAzh0IYj8G2kv5a4I5gHONFN7X3tsYV+hTsr+Oi8/4RzYGzHKjN+VyR -73YaIAY2pQACuz6QBsx1Gd4EhGnobCAwHqRoad5fsa6LQorToEkF1ZNH7262zZzZ -mrG9Ke2MTIWbW1GQbnBj1/UAl1FAzF82zYrgCvNaM/B9HnALPqLTnP3I4Am+WGkm -/3yEfxI9RO56Rub5VeX/rW/6YORjrv0s7SgFB2ec+zG0zKSmQwx9H2QGL1GJhgMz -c+oCf+hEHMBVa2leQoDJHTUvLlT+gHf+RqB/FWGmeDWpXwj3wY+EUiS3HCLPwcPj -xQxQJZACPqFDaxSKyFH9JdfOojYqhmK+ ------END CERTIFICATE REQUEST----- diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf deleted file mode 100644 index d42a92f42e..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/safessl-easyrsa.cnf +++ /dev/null @@ -1,138 +0,0 @@ -# For use with Easy-RSA 3.0+ and OpenSSL or LibreSSL - -#################################################################### -[ ca ] -default_ca = CA_default # The default ca section - -#################################################################### -[ CA_default ] - -dir = /etc/openvpn/pki # Where everything is kept -certs = /etc/openvpn/pki # Where the issued certs are kept -crl_dir = /etc/openvpn/pki # Where the issued crl are kept -database = /etc/openvpn/pki/index.txt # database index file. -new_certs_dir = /etc/openvpn/pki/certs_by_serial # default place for new certs. - -certificate = /etc/openvpn/pki/ca.crt # The CA certificate -serial = /etc/openvpn/pki/serial # The current serial number -crl = /etc/openvpn/pki/crl.pem # The current CRL -private_key = /etc/openvpn/pki/private/ca.key # The private key -RANDFILE = /etc/openvpn/pki/.rand # private random number file - -x509_extensions = basic_exts # The extensions to add to the cert - -# This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA -# is designed for will. In return, we get the Issuer attached to CRLs. -crl_extensions = crl_ext - -default_days = 825 # how long to certify for -default_crl_days= 3650 # how long before next CRL -default_md = sha256 # use public key default MD -preserve = no # keep passed DN ordering - -# This allows to renew certificates which have not been revoked -unique_subject = no - -# A few different ways of specifying how similar the request should look -# For type CA, the listed attributes must be the same, and the optional -# and supplied fields are just that :-) -policy = policy_anything - -# For the 'anything' policy, which defines allowed DN fields -[ policy_anything ] -countryName = optional -stateOrProvinceName = optional -localityName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -name = optional -emailAddress = optional - -#################################################################### -# Easy-RSA request handling -# We key off $DN_MODE to determine how to format the DN -[ req ] -default_bits = 2048 -default_keyfile = privkey.pem -default_md = sha256 -distinguished_name = cn_only -x509_extensions = easyrsa_ca # The extensions to add to the self signed cert - -# A placeholder to handle the $EXTRA_EXTS feature: -#%EXTRA_EXTS% # Do NOT remove or change this line as $EXTRA_EXTS support requires it - -#################################################################### -# Easy-RSA DN (Subject) handling - -# Easy-RSA DN for cn_only support: -[ cn_only ] -commonName = Common Name (eg: your user, host, or server name) -commonName_max = 64 -commonName_default = ChangeMe - -# Easy-RSA DN for org support: -[ org ] -countryName = Country Name (2 letter code) -countryName_default = US -countryName_min = 2 -countryName_max = 2 - -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default = California - -localityName = Locality Name (eg, city) -localityName_default = San Francisco - -0.organizationName = Organization Name (eg, company) -0.organizationName_default = Copyleft Certificate Co - -organizationalUnitName = Organizational Unit Name (eg, section) -organizationalUnitName_default = My Organizational Unit - -commonName = Common Name (eg: your user, host, or server name) -commonName_max = 64 -commonName_default = ChangeMe - -emailAddress = Email Address -emailAddress_default = me@example.net -emailAddress_max = 64 - -#################################################################### -# Easy-RSA cert extension handling - -# This section is effectively unused as the main script sets extensions -# dynamically. This core section is left to support the odd usecase where -# a user calls openssl directly. -[ basic_exts ] -basicConstraints = CA:FALSE -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer:always - -# The Easy-RSA CA extensions -[ easyrsa_ca ] - -# PKIX recommendations: - -subjectKeyIdentifier=hash -authorityKeyIdentifier=keyid:always,issuer:always - -# This could be marked critical, but it's nice to support reading by any -# broken clients who attempt to do so. -basicConstraints = CA:true - -# Limit key usage to CA tasks. If you really want to use the generated pair as -# a self-signed cert, comment this out. -keyUsage = cRLSign, keyCertSign - -# nsCertType omitted by default. Let's try to let the deprecated stuff die. -# nsCertType = sslCA - -# CRL extensions. -[ crl_ext ] - -# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. - -# issuerAltName=issuer:copy -authorityKeyIdentifier=keyid:always,issuer:always - diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial deleted file mode 100644 index ae8764b339..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial +++ /dev/null @@ -1 +0,0 @@ -8055804ACAE0109030FB7947F31147AA diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old deleted file mode 100644 index 32e19cae7a..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/serial.old +++ /dev/null @@ -1 +0,0 @@ -8055804acae0109030fb7947f31147a9 diff --git a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key b/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key deleted file mode 100644 index c0d007b4fe..0000000000 --- a/tests/playwright/rte/openvpn/openvpn-data/conf/pki/ta.key +++ /dev/null @@ -1,21 +0,0 @@ -# -# 2048 bit OpenVPN static key -# ------BEGIN OpenVPN Static key V1----- -456226c4d5a6895c48dad7fd5d36ee57 -3eb280683fbbfe1699d63e9fd4e5ec5b -70500489c3ec36e0c30d6b18f9c48b6b -aede839a99d492fd26beb51317c08eb2 -ebb320a0b980da0b13a88e37559594f5 -03b21fa6d72548f7be5fdb41ad1de315 -82373a95c5c503c1101236f43a59ec68 -ddc9a83d4b4a4437f2db9e16bcbd433a -5211d060bc8376f1efe99bbf2413e543 -4e4473d5028c95f33ad5df3637505c31 -bb7661b03e7d882c3ec1c5ca5f9c2277 -09e2e4323392efb0dff0abadbe6d6887 -27bddf4a2f7f795fe7c227813f76cac2 -9e919074c638ad36e5001a187d113c4b -3faab93dde06734c15a198ad686a315a -3e1f91612528d4f6c4281916625e54b1 ------END OpenVPN Static key V1----- diff --git a/tests/playwright/rte/openvpn/test.ovpn b/tests/playwright/rte/openvpn/test.ovpn deleted file mode 100644 index a6124a8815..0000000000 --- a/tests/playwright/rte/openvpn/test.ovpn +++ /dev/null @@ -1,109 +0,0 @@ - -client -nobind -dev tun -remote-cert-tls server - -remote 127.0.0.1 1194 udp - - ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIppK+RhgYEmsCAggA -MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECG/rQJN9VfmpBIIEyMxNUosBZiWi -7ziO5LeP7tTeeNtTElo/BG7UgDfExEVyhfUvi/VF5yPNoGGzpIfSlUIdxs/IhkMA -qvBBgHGZUzHTrPcxPv3HFA99MQ3sOXbLxV28ilzt/IHyNb2XuP6IHqmgdOIKIdJq -0ln+WsToJLSJQy0wm56PJjcV5pnh+99wNQxnUO8N4PME+y1pkauBJ++i3y4t86Jn -hJJd2ViSnJQ6B4xe9+iaV1qxGNq/b35T7P38hUI3r5FmmX3k8OFScmpExuSJKkwy -G2PS5Q9V8jgYKCDHFqzdhRNMsfFOaeA4y6wlf8Vs8EvvFgwZuNWMZHLXrkLdsk/S -pmVSMQZB584+2y/LrUATKo72Whx3yXxNxBXLL3/vqHef2luCDQMgxVHnTEnczppA -2ABhgqXCz5/RwYiOL1ZIXrHpvL/j1UmCyZBA695hBNOLtSDwBmVeFaMZpfsQqu/h -624mbu/ifSYyXDtrd4XFISqSIEd1eAzXSQNS90UWzxQJQ13Yv57PBPnCvbUxWiuI -UhkHfgldYLvoswZ8B6Eenfw4b2EguFEksaYtrdiJ1H4F2EHP5i/ecXMFfBZ6f5+G -SDzkODUUnniIW3j6XQKRERWp5t1bSYopSWLI259ntmGHTe6zzRjCf4D2POJxA6cy -NDBXd1xxMZnjhDXO8y4vg8MIp4WhvNMvBAOS85mb2cFbL3eYZDokFe0XhTjEklai -YdLSTGa0aU2x5u6FDGulvSwBSuHknHFfk9ABOZ0bVpMr7pYWQ0M/lB+OdU+msbkx -liMVyrnsZjTfYhv/X8HQUKy087i7D6FNtcmNENkJKN7AAjnDaPiBizdMVWNSKrEa -ox+V1uWpXba0HFtl57V1c3FvuXYtpQ7vSk3BrJMtSkkWalWmCxE6df7nJeygG4tj -CskvgR75N6TOiNK6C8gKXZbirufICH47hJHzPbfDK+Wwl+tccXqL+5rcqGpqaHGw -ED3Y5QkqLuk0wCBiyyuEX4E+l/Is64dRaiahjqp2M/SA4zuk1kIhEPRGyN9W6jx4 -T3yocMdXq7N87O+gA5XW+TPZlDxZeGIJK2V1qWpuvdbBFbT/PEdXVbFhErEVs2Vp -KIxWGYxeajOruPdHBAbLtJaWiqqG84QCglbd240eG7fXse+onVHa9r0mhIgBdzOa -Keheq5Wrjf6Zk9ljnzCEl79Bg92ZTWwOmOsXAWVL0eHS9VOkuSfjIVhEP0D69fxE -dOeMB5ECy5vlnejkRnM0jtfaRuM+7ZNydKAL0z3gKjnBzYotNu8qcw/Pf/+c5qy3 -M1kZGeY/y+wdj+vHC9Tmi8dk21+ZEuZJ8Yb0kvv3lSo5UHdv8OxxQyVQdXc8M/Nx -ZiMo8lryBhNbK6AIguukcTUo7HXXLg7+M78H7VKq/syZJvBVU9RT0NU7Z+amFgh+ -YXQPnu5IUCPAxTIEU0f17GXMLLkp2rRNMLiJVGZmyD+BTkWhc7z+z4jgtR16gqUA -GBBzYI3ZlOekfNEKIemLYn3AaPAB79myaJxVsy/9KzNj2xv3r2ClAF0EWJLnhMrq -dqHqhxkqjTeDAfqJwNrxWJ7Wvp5I9uJ+a2wIyrY5gluhEcdiaw3ehB9/ykLAMvtQ -lpntxcSG4bJTbVuDW/JNRQ== ------END ENCRYPTED PRIVATE KEY----- - - ------BEGIN CERTIFICATE----- -MIIDRTCCAi2gAwIBAgIRAIBVgErK4BCQMPt5R/MRR6kwDQYJKoZIhvcNAQELBQAw -DzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzUxNTZaFw0yNDAyMTEwNzUxNTZa -MA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDNujpXmw+b3VrDjKzwJC4gjjszbYayy4EAg0pPFkDNs+UdxX+Y50ug9W/zWxui -axZNXFv+RsNYjg4T+expaDf2feB+i5UPcbqJsV0OynybngdXwkvjQpbvXkPq/hHy -ODq0DOPiTCjiB7uaVmOYiJEV9SdNpdGIDElIJI9xjX0OSBvZlaR78rf2aJUMFC8Z -jazFzZWsQpOrbmAzQJD2gE6oS/AP1NbAXfKP3cBBK3iWEmA358XMuno23grw5cmQ -UT1mptG50rTTrcty+UZFM2VK4+mV7iM3krBrqJUCBgRrfkSpTjz9k1syTMNAJJ5S -FNGsqsWIS4h1URyWJsLXdcZ7AgMBAAGjgZswgZgwCQYDVR0TBAIwADAdBgNVHQ4E -FgQUjp4ln09TbR3WLAgDLGbDb5AWU5YwSgYDVR0jBEMwQYAUPwGq0LHvvgK4J9XB -8b16BvFvj3OhE6QRMA8xDTALBgNVBAMMBHRlc3SCFHKh49rkvHCvUmEIiZqCEf+3 -1ND6MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0B -AQsFAAOCAQEAAGfSk+Zpy/32n9/2WSAq9g8DurPaZd5iIzaijU8nIg49AYDZWc3F -8Bqbxej0b+LHKfw3IS9vnbiM9m43ybFKDZ3lzQpLAQqYj0bpJJec73XdpPczfd8J -80y2PDinLiYdaPmHmq5sYNneMvFpZpfLIIENtQF0tXOMhStac+rN5SUTRDokCgpy -TULMC1rJlgUgN/sblRiNZv8Q+DrYA2xsN27eUVkIftEzEQh07fw/TRkAgoiflWam -6fFzVeF6P66m4bdR35IoGUIdpaftueQA6qdV41USRV/54aUfE/HuGjHlrp0u793Y -VrJ89roIQdsTFjEOXUGybZgB5EOi0TSekQ== ------END CERTIFICATE----- - - ------BEGIN CERTIFICATE----- -MIIDNjCCAh6gAwIBAgIUcqHj2uS8cK9SYQiJmoIR/7fU0PowDQYJKoZIhvcNAQEL -BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yMTExMDgwNzQ5MTBaFw0zMTExMDYwNzQ5 -MTBaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQCibt8kh9lqTC0O631rPHN0kMQ4kMQ/eZ59mKhAJZ3rBchIBrQne2yTw2z+ -X1ESa3VTkW2jyJ5r7iuo+Xyc8246tfBwO3u0DJ2DeZZOYPzMg48nJNxs3ur3iXAT -r6Aiwp0gtMNC2XcW7y5OPl8l+BhSt2PsWcdEdmLJgvRPJ2x+Ea8wivuw6FO6byK7 -Mxw7/CbNMw8Eey9eSz9kWDrgetS0kOgfqtt1ZnKDZkbLy8jFl0xW488VUrefUR1g -lOje8QySjDvzT8sUR0lASyS+/J6j/3gLlSS42e4SxMz00jEus+ye56cO16Pc+vKI -Xsev8cRPiSDTZTvc7Eaq/OcKVl11AgMBAAGjgYkwgYYwHQYDVR0OBBYEFD8BqtCx -774CuCfVwfG9egbxb49zMEoGA1UdIwRDMEGAFD8BqtCx774CuCfVwfG9egbxb49z -oROkETAPMQ0wCwYDVQQDDAR0ZXN0ghRyoePa5Lxwr1JhCImaghH/t9TQ+jAMBgNV -HRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAXUrDhVAX -TkbhKRBuhUGQb03RyACQKBFM/SwhrmwpQMXo7BUuqWJ27U5/TRHrfKJxDgppmwIs -qmtrT07tA7e/OyFSZtZ9p/4H+5xM9FCsmu6YMQ3ZloHHGWmibrDNK70frVgRAEAS -FyAsEgpKZCr6OJNd7v2dbvO4AniZVVvccU17cJAx177YC3fNIuRtpHkm93D3qI+1 -4SED7rktVfXUKs6RMFmqIum5WRzgiJBAtk2GVQMrAAu/xmUPS/aqzstNte4KQ+UY -2qI9v1wYM8j+BT5nsBT02K+zOsYdkG39n7QEfcecPAjOkKsaFbSf/WZcsb6oCVgl -d/Nz24kfh76SqQ== ------END CERTIFICATE----- - -key-direction 1 - -# -# 2048 bit OpenVPN static key -# ------BEGIN OpenVPN Static key V1----- -456226c4d5a6895c48dad7fd5d36ee57 -3eb280683fbbfe1699d63e9fd4e5ec5b -70500489c3ec36e0c30d6b18f9c48b6b -aede839a99d492fd26beb51317c08eb2 -ebb320a0b980da0b13a88e37559594f5 -03b21fa6d72548f7be5fdb41ad1de315 -82373a95c5c503c1101236f43a59ec68 -ddc9a83d4b4a4437f2db9e16bcbd433a -5211d060bc8376f1efe99bbf2413e543 -4e4473d5028c95f33ad5df3637505c31 -bb7661b03e7d882c3ec1c5ca5f9c2277 -09e2e4323392efb0dff0abadbe6d6887 -27bddf4a2f7f795fe7c227813f76cac2 -9e919074c638ad36e5001a187d113c4b -3faab93dde06734c15a198ad686a315a -3e1f91612528d4f6c4281916625e54b1 ------END OpenVPN Static key V1----- - - diff --git a/tests/playwright/rte/oss-cluster-7-rs/Dockerfile b/tests/playwright/rte/oss-cluster-7-rs/Dockerfile deleted file mode 100644 index b3f1a97011..0000000000 --- a/tests/playwright/rte/oss-cluster-7-rs/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM redislabs/rejson:1.0.8 as rejson - -FROM redis:7.0.8 - -COPY redis.conf /etc/redis/ -COPY --from=rejson /usr/lib/redis/modules/rejson.so /etc/redis/modules/ - -CMD ls -la -RUN ls -la /etc/redis/modules - -ADD https://s3.amazonaws.com/redisinsight.test/public/rte/modules/redisearch-coord/redisearch-coord.so.tar.gz . - -RUN ls -la -RUN ls -la /etc/redis/modules -RUN tar -xvzf redisearch-coord.so.tar.gz && rm redisearch-coord.so.tar.gz && cp redisearch-coord.so /etc/redis/modules -RUN ls -la -RUN ls -la /etc/redis/modules - -CMD [ "redis-server", "/etc/redis/redis.conf" ] diff --git a/tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh b/tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh deleted file mode 100644 index a21401b4df..0000000000 --- a/tests/playwright/rte/oss-cluster-7-rs/cluster-rs-create.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -echo 'Try to sleep for a while...' -sleep 25 -echo 'Creating cluster...' -echo "yes" | redis-cli \ - --cluster create \ - 172.31.100.221:6379 \ - 172.31.100.222:6379 \ - 172.31.100.223:6379 \ - --cluster-replicas 0 \ - && redis-server diff --git a/tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile b/tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile deleted file mode 100644 index 24762ba80e..0000000000 --- a/tests/playwright/rte/oss-cluster-7-rs/creator.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM redis:7.0.6 - -USER root - -COPY cluster-rs-create.sh ./ - -RUN chmod a+x cluster-rs-create.sh - -CMD ["/bin/sh", "./cluster-rs-create.sh"] diff --git a/tests/playwright/rte/oss-cluster-7-rs/redis.conf b/tests/playwright/rte/oss-cluster-7-rs/redis.conf deleted file mode 100644 index 1c7ab660d5..0000000000 --- a/tests/playwright/rte/oss-cluster-7-rs/redis.conf +++ /dev/null @@ -1,1881 +0,0 @@ -# Redis configuration file example. -# -# Note that in order to read the configuration file, Redis must be -# started with the file path as first argument: -# -# ./redis-server /path/to/redis.conf - -# Note on units: when memory size is needed, it is possible to specify -# it in the usual form of 1k 5GB 4M and so forth: -# -# 1k => 1000 bytes -# 1kb => 1024 bytes -# 1m => 1000000 bytes -# 1mb => 1024*1024 bytes -# 1g => 1000000000 bytes -# 1gb => 1024*1024*1024 bytes -# -# units are case insensitive so 1GB 1Gb 1gB are all the same. - -################################## INCLUDES ################################### - -# Include one or more other config files here. This is useful if you -# have a standard template that goes to all Redis servers but also need -# to customize a few per-server settings. Include files can include -# other files, so use this wisely. -# -# Note that option "include" won't be rewritten by command "CONFIG REWRITE" -# from admin or Redis Sentinel. Since Redis always uses the last processed -# line as value of a configuration directive, you'd better put includes -# at the beginning of this file to avoid overwriting config change at runtime. -# -# If instead you are interested in using includes to override configuration -# options, it is better to use include as the last line. -# -# include /path/to/local.conf -# include /path/to/other.conf - -################################## MODULES ##################################### - -# Load modules at startup. If the server is not able to load modules -# it will abort. It is possible to use multiple loadmodule directives. -# -loadmodule /etc/redis/modules/rejson.so -loadmodule /etc/redis/modules/redisearch-coord.so -# loadmodule /path/to/other_module.so - -################################## NETWORK ##################################### - -# By default, if no "bind" configuration directive is specified, Redis listens -# for connections from all available network interfaces on the host machine. -# It is possible to listen to just one or multiple selected interfaces using -# the "bind" configuration directive, followed by one or more IP addresses. -# -# Examples: -# -# bind 192.168.1.100 10.0.0.1 -# bind 127.0.0.1 ::1 -# -# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the -# internet, binding to all the interfaces is dangerous and will expose the -# instance to everybody on the internet. So by default we uncomment the -# following bind directive, that will force Redis to listen only on the -# IPv4 loopback interface address (this means Redis will only be able to -# accept client connections from the same host that it is running on). -# -# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES -# JUST COMMENT OUT THE FOLLOWING LINE. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# bind 127.0.0.1 - -# Protected mode is a layer of security protection, in order to avoid that -# Redis instances left open on the internet are accessed and exploited. -# -# When protected mode is on and if: -# -# 1) The server is not binding explicitly to a set of addresses using the -# "bind" directive. -# 2) No password is configured. -# -# The server only accepts connections from clients connecting from the -# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain -# sockets. -# -# By default protected mode is enabled. You should disable it only if -# you are sure you want clients from other hosts to connect to Redis -# even if no authentication is configured, nor a specific set of interfaces -# are explicitly listed using the "bind" directive. -# protected-mode yes - -# Accept connections on the specified port, default is 6379 (IANA #815344). -# If port 0 is specified Redis will not listen on a TCP socket. -port 6379 - -# TCP listen() backlog. -# -# In high requests-per-second environments you need a high backlog in order -# to avoid slow clients connection issues. Note that the Linux kernel -# will silently truncate it to the value of /proc/sys/net/core/somaxconn so -# make sure to raise both the value of somaxconn and tcp_max_syn_backlog -# in order to get the desired effect. -tcp-backlog 511 - -# Unix socket. -# -# Specify the path for the Unix socket that will be used to listen for -# incoming connections. There is no default, so Redis will not listen -# on a unix socket when not specified. -# -# unixsocket /tmp/redis.sock -# unixsocketperm 700 - -# Close the connection after a client is idle for N seconds (0 to disable) -timeout 0 - -# TCP keepalive. -# -# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence -# of communication. This is useful for two reasons: -# -# 1) Detect dead peers. -# 2) Force network equipment in the middle to consider the connection to be -# alive. -# -# On Linux, the specified value (in seconds) is the period used to send ACKs. -# Note that to close the connection the double of the time is needed. -# On other kernels the period depends on the kernel configuration. -# -# A reasonable value for this option is 300 seconds, which is the new -# Redis default starting with Redis 3.2.1. -tcp-keepalive 300 - -################################# TLS/SSL ##################################### - -# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration -# directive can be used to define TLS-listening ports. To enable TLS on the -# default port, use: -# -# port 0 -# tls-port 6379 - -# Configure a X.509 certificate and private key to use for authenticating the -# server to connected clients, masters or cluster peers. These files should be -# PEM formatted. -# -# tls-cert-file /etc/redis/redis.crt -# tls-key-file /etc/redis/redis.key - -# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: -# -# tls-dh-params-file redis.dh - -# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL -# clients and peers. Redis requires an explicit configuration of at least one -# of these, and will not implicitly use the system wide configuration. -# -# tls-ca-cert-file /etc/redis/ca.crt -# tls-ca-cert-dir /etc/ssl/certs - -# By default, clients (including replica servers) on a TLS port are required -# to authenticate using valid client side certificates. -# -# If "no" is specified, client certificates are not required and not accepted. -# If "optional" is specified, client certificates are accepted and must be -# valid if provided, but are not required. -# -# tls-auth-clients yes -# tls-auth-clients optional - -# By default, a Redis replica does not attempt to establish a TLS connection -# with its master. -# -# Use the following directive to enable TLS on replication links. -# -# tls-replication yes - -# By default, the Redis Cluster bus uses a plain TCP connection. To enable -# TLS for the bus protocol, use the following directive: -# -# tls-cluster yes - -# Explicitly specify TLS versions to support. Allowed values are case insensitive -# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or -# any combination. To enable only TLSv1.2 and TLSv1.3, use: -# -# tls-protocols "TLSv1.2 TLSv1.3" - -# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information -# about the syntax of this string. -# -# Note: this configuration applies only to <= TLSv1.2. -# -# tls-ciphers DEFAULT:!MEDIUM - -# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more -# information about the syntax of this string, and specifically for TLSv1.3 -# ciphersuites. -# -# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 - -# When choosing a cipher, use the server's preference instead of the client -# preference. By default, the server follows the client's preference. -# -# tls-prefer-server-ciphers yes - -# By default, TLS session caching is enabled to allow faster and less expensive -# reconnections by clients that support it. Use the following directive to disable -# caching. -# -# tls-session-caching no - -# Change the default number of TLS sessions cached. A zero value sets the cache -# to unlimited size. The default size is 20480. -# -# tls-session-cache-size 5000 - -# Change the default timeout of cached TLS sessions. The default timeout is 300 -# seconds. -# -# tls-session-cache-timeout 60 - -################################# GENERAL ##################################### - -# By default Redis does not run as a daemon. Use 'yes' if you need it. -# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. -daemonize no - -# If you run Redis from upstart or systemd, Redis can interact with your -# supervision tree. Options: -# supervised no - no supervision interaction -# supervised upstart - signal upstart by putting Redis into SIGSTOP mode -# requires "expect stop" in your upstart job config -# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET -# supervised auto - detect upstart or systemd method based on -# UPSTART_JOB or NOTIFY_SOCKET environment variables -# Note: these supervision methods only signal "process is ready." -# They do not enable continuous pings back to your supervisor. -supervised no - -# If a pid file is specified, Redis writes it where specified at startup -# and removes it at exit. -# -# When the server runs non daemonized, no pid file is created if none is -# specified in the configuration. When the server is daemonized, the pid file -# is used even if not specified, defaulting to "/var/run/redis.pid". -# -# Creating a pid file is best effort: if Redis is not able to create it -# nothing bad happens, the server will start and run normally. -pidfile /var/run/redis_6379.pid - -# Specify the server verbosity level. -# This can be one of: -# debug (a lot of information, useful for development/testing) -# verbose (many rarely useful info, but not a mess like the debug level) -# notice (moderately verbose, what you want in production probably) -# warning (only very important / critical messages are logged) -loglevel notice - -# Specify the log file name. Also the empty string can be used to force -# Redis to log on the standard output. Note that if you use standard -# output for logging but daemonize, logs will be sent to /dev/null -logfile "" - -# To enable logging to the system logger, just set 'syslog-enabled' to yes, -# and optionally update the other syslog parameters to suit your needs. -# syslog-enabled no - -# Specify the syslog identity. -# syslog-ident redis - -# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. -# syslog-facility local0 - -# Set the number of databases. The default database is DB 0, you can select -# a different one on a per-connection basis using SELECT where -# dbid is a number between 0 and 'databases'-1 -databases 16 - -# By default Redis shows an ASCII art logo only when started to log to the -# standard output and if the standard output is a TTY. Basically this means -# that normally a logo is displayed only in interactive sessions. -# -# However it is possible to force the pre-4.0 behavior and always show a -# ASCII art logo in startup logs by setting the following option to yes. -always-show-logo yes - -################################ SNAPSHOTTING ################################ -# -# Save the DB on disk: -# -# save -# -# Will save the DB if both the given number of seconds and the given -# number of write operations against the DB occurred. -# -# In the example below the behavior will be to save: -# after 900 sec (15 min) if at least 1 key changed -# after 300 sec (5 min) if at least 10 keys changed -# after 60 sec if at least 10000 keys changed -# -# Note: you can disable saving completely by commenting out all "save" lines. -# -# It is also possible to remove all the previously configured save -# points by adding a save directive with a single empty string argument -# like in the following example: -# -# save "" - -save 900 1 -save 300 10 -save 60 10000 - -# By default Redis will stop accepting writes if RDB snapshots are enabled -# (at least one save point) and the latest background save failed. -# This will make the user aware (in a hard way) that data is not persisting -# on disk properly, otherwise chances are that no one will notice and some -# disaster will happen. -# -# If the background saving process will start working again Redis will -# automatically allow writes again. -# -# However if you have setup your proper monitoring of the Redis server -# and persistence, you may want to disable this feature so that Redis will -# continue to work as usual even if there are problems with disk, -# permissions, and so forth. -stop-writes-on-bgsave-error yes - -# Compress string objects using LZF when dump .rdb databases? -# By default compression is enabled as it's almost always a win. -# If you want to save some CPU in the saving child set it to 'no' but -# the dataset will likely be bigger if you have compressible values or keys. -rdbcompression yes - -# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. -# This makes the format more resistant to corruption but there is a performance -# hit to pay (around 10%) when saving and loading RDB files, so you can disable it -# for maximum performances. -# -# RDB files created with checksum disabled have a checksum of zero that will -# tell the loading code to skip the check. -rdbchecksum yes - -# The filename where to dump the DB -dbfilename dump.rdb - -# Remove RDB files used by replication in instances without persistence -# enabled. By default this option is disabled, however there are environments -# where for regulations or other security concerns, RDB files persisted on -# disk by masters in order to feed replicas, or stored on disk by replicas -# in order to load them for the initial synchronization, should be deleted -# ASAP. Note that this option ONLY WORKS in instances that have both AOF -# and RDB persistence disabled, otherwise is completely ignored. -# -# An alternative (and sometimes better) way to obtain the same effect is -# to use diskless replication on both master and replicas instances. However -# in the case of replicas, diskless is not always an option. -# in the case of replicas, diskless is not always an option. -rdb-del-sync-files no - -# The working directory. -# -# The DB will be written inside this directory, with the filename specified -# above using the 'dbfilename' configuration directive. -# -# The Append Only File will also be created inside this directory. -# -# Note that you must specify a directory here, not a file name. -dir ./ - -################################# REPLICATION ################################# - -# Master-Replica replication. Use replicaof to make a Redis instance a copy of -# another Redis server. A few things to understand ASAP about Redis replication. -# -# +------------------+ +---------------+ -# | Master | ---> | Replica | -# | (receive writes) | | (exact copy) | -# +------------------+ +---------------+ -# -# 1) Redis replication is asynchronous, but you can configure a master to -# stop accepting writes if it appears to be not connected with at least -# a given number of replicas. -# 2) Redis replicas are able to perform a partial resynchronization with the -# master if the replication link is lost for a relatively small amount of -# time. You may want to configure the replication backlog size (see the next -# sections of this file) with a sensible value depending on your needs. -# 3) Replication is automatic and does not need user intervention. After a -# network partition replicas automatically try to reconnect to masters -# and resynchronize with them. -# -# replicaof - -# If the master is password protected (using the "requirepass" configuration -# directive below) it is possible to tell the replica to authenticate before -# starting the replication synchronization process, otherwise the master will -# refuse the replica request. -# -# masterauth defaultpass -# -# However this is not enough if you are using Redis ACLs (for Redis version -# 6 or greater), and the default user is not capable of running the PSYNC -# command and/or other commands needed for replication. In this case it's -# better to configure a special user to use with replication, and specify the -# masteruser configuration as such: -# -# masteruser -# -# When masteruser is specified, the replica will authenticate against its -# master using the new AUTH form: AUTH . - -# When a replica loses its connection with the master, or when the replication -# is still in progress, the replica can act in two different ways: -# -# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will -# still reply to client requests, possibly with out of date data, or the -# data set may just be empty if this is the first synchronization. -# -# 2) If replica-serve-stale-data is set to 'no' the replica will reply with -# an error "SYNC with master in progress" to all commands except: -# INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, -# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, -# HOST and LATENCY. -# -replica-serve-stale-data yes - -# You can configure a replica instance to accept writes or not. Writing against -# a replica instance may be useful to store some ephemeral data (because data -# written on a replica will be easily deleted after resync with the master) but -# may also cause problems if clients are writing to it because of a -# misconfiguration. -# -# Since Redis 2.6 by default replicas are read-only. -# -# Note: read only replicas are not designed to be exposed to untrusted clients -# on the internet. It's just a protection layer against misuse of the instance. -# Still a read only replica exports by default all the administrative commands -# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve -# security of read only replicas using 'rename-command' to shadow all the -# administrative / dangerous commands. -replica-read-only yes - -# Replication SYNC strategy: disk or socket. -# -# New replicas and reconnecting replicas that are not able to continue the -# replication process just receiving differences, need to do what is called a -# "full synchronization". An RDB file is transmitted from the master to the -# replicas. -# -# The transmission can happen in two different ways: -# -# 1) Disk-backed: The Redis master creates a new process that writes the RDB -# file on disk. Later the file is transferred by the parent -# process to the replicas incrementally. -# 2) Diskless: The Redis master creates a new process that directly writes the -# RDB file to replica sockets, without touching the disk at all. -# -# With disk-backed replication, while the RDB file is generated, more replicas -# can be queued and served with the RDB file as soon as the current child -# producing the RDB file finishes its work. With diskless replication instead -# once the transfer starts, new replicas arriving will be queued and a new -# transfer will start when the current one terminates. -# -# When diskless replication is used, the master waits a configurable amount of -# time (in seconds) before starting the transfer in the hope that multiple -# replicas will arrive and the transfer can be parallelized. -# -# With slow disks and fast (large bandwidth) networks, diskless replication -# works better. -repl-diskless-sync no - -# When diskless replication is enabled, it is possible to configure the delay -# the server waits in order to spawn the child that transfers the RDB via socket -# to the replicas. -# -# This is important since once the transfer starts, it is not possible to serve -# new replicas arriving, that will be queued for the next RDB transfer, so the -# server waits a delay in order to let more replicas arrive. -# -# The delay is specified in seconds, and by default is 5 seconds. To disable -# it entirely just set it to 0 seconds and the transfer will start ASAP. -repl-diskless-sync-delay 5 - -# ----------------------------------------------------------------------------- -# WARNING: RDB diskless load is experimental. Since in this setup the replica -# does not immediately store an RDB on disk, it may cause data loss during -# failovers. RDB diskless load + Redis modules not handling I/O reads may also -# cause Redis to abort in case of I/O errors during the initial synchronization -# stage with the master. Use only if your do what you are doing. -# ----------------------------------------------------------------------------- -# -# Replica can load the RDB it reads from the replication link directly from the -# socket, or store the RDB to a file and read that file after it was completely -# received from the master. -# -# In many cases the disk is slower than the network, and storing and loading -# the RDB file may increase replication time (and even increase the master's -# Copy on Write memory and salve buffers). -# However, parsing the RDB file directly from the socket may mean that we have -# to flush the contents of the current database before the full rdb was -# received. For this reason we have the following options: -# -# "disabled" - Don't use diskless load (store the rdb file to the disk first) -# "on-empty-db" - Use diskless load only when it is completely safe. -# "swapdb" - Keep a copy of the current db contents in RAM while parsing -# the data directly from the socket. note that this requires -# sufficient memory, if you don't have it, you risk an OOM kill. -repl-diskless-load disabled - -# Replicas send PINGs to server in a predefined interval. It's possible to -# change this interval with the repl_ping_replica_period option. The default -# value is 10 seconds. -# -# repl-ping-replica-period 10 - -# The following option sets the replication timeout for: -# -# 1) Bulk transfer I/O during SYNC, from the point of view of replica. -# 2) Master timeout from the point of view of replicas (data, pings). -# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). -# -# It is important to make sure that this value is greater than the value -# specified for repl-ping-replica-period otherwise a timeout will be detected -# every time there is low traffic between the master and the replica. The default -# value is 60 seconds. -# -# repl-timeout 60 - -# Disable TCP_NODELAY on the replica socket after SYNC? -# -# If you select "yes" Redis will use a smaller number of TCP packets and -# less bandwidth to send data to replicas. But this can add a delay for -# the data to appear on the replica side, up to 40 milliseconds with -# Linux kernels using a default configuration. -# -# If you select "no" the delay for data to appear on the replica side will -# be reduced but more bandwidth will be used for replication. -# -# By default we optimize for low latency, but in very high traffic conditions -# or when the master and replicas are many hops away, turning this to "yes" may -# be a good idea. -repl-disable-tcp-nodelay no - -# Set the replication backlog size. The backlog is a buffer that accumulates -# replica data when replicas are disconnected for some time, so that when a -# replica wants to reconnect again, often a full resync is not needed, but a -# partial resync is enough, just passing the portion of data the replica -# missed while disconnected. -# -# The bigger the replication backlog, the longer the replica can endure the -# disconnect and later be able to perform a partial resynchronization. -# -# The backlog is only allocated if there is at least one replica connected. -# -# repl-backlog-size 1mb - -# After a master has no connected replicas for some time, the backlog will be -# freed. The following option configures the amount of seconds that need to -# elapse, starting from the time the last replica disconnected, for the backlog -# buffer to be freed. -# -# Note that replicas never free the backlog for timeout, since they may be -# promoted to masters later, and should be able to correctly "partially -# resynchronize" with other replicas: hence they should always accumulate backlog. -# -# A value of 0 means to never release the backlog. -# -# repl-backlog-ttl 3600 - -# The replica priority is an integer number published by Redis in the INFO -# output. It is used by Redis Sentinel in order to select a replica to promote -# into a master if the master is no longer working correctly. -# -# A replica with a low priority number is considered better for promotion, so -# for instance if there are three replicas with priority 10, 100, 25 Sentinel -# will pick the one with priority 10, that is the lowest. -# -# However a special priority of 0 marks the replica as not able to perform the -# role of master, so a replica with priority of 0 will never be selected by -# Redis Sentinel for promotion. -# -# By default the priority is 100. -replica-priority 100 - -# It is possible for a master to stop accepting writes if there are less than -# N replicas connected, having a lag less or equal than M seconds. -# -# The N replicas need to be in "online" state. -# -# The lag in seconds, that must be <= the specified value, is calculated from -# the last ping received from the replica, that is usually sent every second. -# -# This option does not GUARANTEE that N replicas will accept the write, but -# will limit the window of exposure for lost writes in case not enough replicas -# are available, to the specified number of seconds. -# -# For example to require at least 3 replicas with a lag <= 10 seconds use: -# -# min-replicas-to-write 3 -# min-replicas-max-lag 10 -# -# Setting one or the other to 0 disables the feature. -# -# By default min-replicas-to-write is set to 0 (feature disabled) and -# min-replicas-max-lag is set to 10. - -# A Redis master is able to list the address and port of the attached -# replicas in different ways. For example the "INFO replication" section -# offers this information, which is used, among other tools, by -# Redis Sentinel in order to discover replica instances. -# Another place where this info is available is in the output of the -# "ROLE" command of a master. -# -# The listed IP address and port normally reported by a replica is -# obtained in the following way: -# -# IP: The address is auto detected by checking the peer address -# of the socket used by the replica to connect with the master. -# -# Port: The port is communicated by the replica during the replication -# handshake, and is normally the port that the replica is using to -# listen for connections. -# -# However when port forwarding or Network Address Translation (NAT) is -# used, the replica may actually be reachable via different IP and port -# pairs. The following two options can be used by a replica in order to -# report to its master a specific set of IP and port, so that both INFO -# and ROLE will report those values. -# -# There is no need to use both the options if you need to override just -# the port or the IP address. -# -# replica-announce-ip 5.5.5.5 -# replica-announce-port 1234 - -############################### KEYS TRACKING ################################# - -# Redis implements server assisted support for client side caching of values. -# This is implemented using an invalidation table that remembers, using -# 16 millions of slots, what clients may have certain subsets of keys. In turn -# this is used in order to send invalidation messages to clients. Please -# check this page to understand more about the feature: -# -# https://redis.io/topics/client-side-caching -# -# When tracking is enabled for a client, all the read only queries are assumed -# to be cached: this will force Redis to store information in the invalidation -# table. When keys are modified, such information is flushed away, and -# invalidation messages are sent to the clients. However if the workload is -# heavily dominated by reads, Redis could use more and more memory in order -# to track the keys fetched by many clients. -# -# For this reason it is possible to configure a maximum fill value for the -# invalidation table. By default it is set to 1M of keys, and once this limit -# is reached, Redis will start to evict keys in the invalidation table -# even if they were not modified, just to reclaim memory: this will in turn -# force the clients to invalidate the cached values. Basically the table -# maximum size is a trade off between the memory you want to spend server -# side to track information about who cached what, and the ability of clients -# to retain cached objects in memory. -# -# If you set the value to 0, it means there are no limits, and Redis will -# retain as many keys as needed in the invalidation table. -# In the "stats" INFO section, you can find information about the number of -# keys in the invalidation table at every given moment. -# -# Note: when key tracking is used in broadcasting mode, no memory is used -# in the server side so this setting is useless. -# -# tracking-table-max-keys 1000000 - -################################## SECURITY ################################### - -# Warning: since Redis is pretty fast, an outside user can try up to -# 1 million passwords per second against a modern box. This means that you -# should use very strong passwords, otherwise they will be very easy to break. -# Note that because the password is really a shared secret between the client -# and the server, and should not be memorized by any human, the password -# can be easily a long string from /dev/urandom or whatever, so by using a -# long and unguessable password no brute force attack will be possible. - -# Redis ACL users are defined in the following format: -# -# user ... acl rules ... -# -# For example: -# -# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 -# -# The special username "default" is used for new connections. If this user -# has the "nopass" rule, then new connections will be immediately authenticated -# as the "default" user without the need of any password provided via the -# AUTH command. Otherwise if the "default" user is not flagged with "nopass" -# the connections will start in not authenticated state, and will require -# AUTH (or the HELLO command AUTH option) in order to be authenticated and -# start to work. -# -# The ACL rules that describe what a user can do are the following: -# -# on Enable the user: it is possible to authenticate as this user. -# off Disable the user: it's no longer possible to authenticate -# with this user, however the already authenticated connections -# will still work. -# + Allow the execution of that command -# - Disallow the execution of that command -# +@ Allow the execution of all the commands in such category -# with valid categories are like @admin, @set, @sortedset, ... -# and so forth, see the full list in the server.c file where -# the Redis command table is described and defined. -# The special category @all means all the commands, but currently -# present in the server, and that will be loaded in the future -# via modules. -# +|subcommand Allow a specific subcommand of an otherwise -# disabled command. Note that this form is not -# allowed as negative like -DEBUG|SEGFAULT, but -# only additive starting with "+". -# allcommands Alias for +@all. Note that it implies the ability to execute -# all the future commands loaded via the modules system. -# nocommands Alias for -@all. -# ~ Add a pattern of keys that can be mentioned as part of -# commands. For instance ~* allows all the keys. The pattern -# is a glob-style pattern like the one of KEYS. -# It is possible to specify multiple patterns. -# allkeys Alias for ~* -# resetkeys Flush the list of allowed keys patterns. -# > Add this password to the list of valid password for the user. -# For example >mypass will add "mypass" to the list. -# This directive clears the "nopass" flag (see later). -# < Remove this password from the list of valid passwords. -# nopass All the set passwords of the user are removed, and the user -# is flagged as requiring no password: it means that every -# password will work against this user. If this directive is -# used for the default user, every new connection will be -# immediately authenticated with the default user without -# any explicit AUTH command required. Note that the "resetpass" -# directive will clear this condition. -# resetpass Flush the list of allowed passwords. Moreover removes the -# "nopass" status. After "resetpass" the user has no associated -# passwords and there is no way to authenticate without adding -# some password (or setting it as "nopass" later). -# reset Performs the following actions: resetpass, resetkeys, off, -# -@all. The user returns to the same state it has immediately -# after its creation. -# -# ACL rules can be specified in any order: for instance you can start with -# passwords, then flags, or key patterns. However note that the additive -# and subtractive rules will CHANGE MEANING depending on the ordering. -# For instance see the following example: -# -# user alice on +@all -DEBUG ~* >somepassword -# -# This will allow "alice" to use all the commands with the exception of the -# DEBUG command, since +@all added all the commands to the set of the commands -# alice can use, and later DEBUG was removed. However if we invert the order -# of two ACL rules the result will be different: -# -# user alice on -DEBUG +@all ~* >somepassword -# -# Now DEBUG was removed when alice had yet no commands in the set of allowed -# commands, later all the commands are added, so the user will be able to -# execute everything. -# -# Basically ACL rules are processed left-to-right. -# -# For more information about ACL configuration please refer to -# the Redis web site at https://redis.io/topics/acl - -# ACL LOG -# -# The ACL Log tracks failed commands and authentication events associated -# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked -# by ACLs. The ACL Log is stored in memory. You can reclaim memory with -# ACL LOG RESET. Define the maximum entry length of the ACL Log below. -acllog-max-len 128 - -# Using an external ACL file -# -# Instead of configuring users here in this file, it is possible to use -# a stand-alone file just listing users. The two methods cannot be mixed: -# if you configure users here and at the same time you activate the external -# ACL file, the server will refuse to start. -# -# The format of the external ACL user file is exactly the same as the -# format that is used inside redis.conf to describe users. -# -# aclfile /etc/redis/users.acl - -# aclfile /etc/redis/users.acl - -# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility -# layer on top of the new ACL system. The option effect will be just setting -# the password for the default user. Clients will still authenticate using -# AUTH as usually, or more explicitly with AUTH default -# if they follow the new protocol: both will work. -# -# requirepass somepass - -# Command renaming (DEPRECATED). -# -# ------------------------------------------------------------------------ -# WARNING: avoid using this option if possible. Instead use ACLs to remove -# commands from the default user, and put them only in some admin user you -# create for administrative purposes. -# ------------------------------------------------------------------------ -# -# It is possible to change the name of dangerous commands in a shared -# environment. For instance the CONFIG command may be renamed into something -# hard to guess so that it will still be available for internal-use tools -# but not available for general clients. -# -# Example: -# -# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 -# -# It is also possible to completely kill a command by renaming it into -# an empty string: -# -# rename-command CONFIG "" -# -# Please note that changing the name of commands that are logged into the -# AOF file or transmitted to replicas may cause problems. - -################################### CLIENTS #################################### - -# Set the max number of connected clients at the same time. By default -# this limit is set to 10000 clients, however if the Redis server is not -# able to configure the process file limit to allow for the specified limit -# the max number of allowed clients is set to the current file limit -# minus 32 (as Redis reserves a few file descriptors for internal uses). -# -# Once the limit is reached Redis will close all the new connections sending -# an error 'max number of clients reached'. -# -# IMPORTANT: When Redis Cluster is used, the max number of connections is also -# shared with the cluster bus: every node in the cluster will use two -# connections, one incoming and another outgoing. It is important to size the -# limit accordingly in case of very large clusters. -# -# maxclients 10000 - -############################## MEMORY MANAGEMENT ################################ - -# Set a memory usage limit to the specified amount of bytes. -# When the memory limit is reached Redis will try to remove keys -# according to the eviction policy selected (see maxmemory-policy). -# -# If Redis can't remove keys according to the policy, or if the policy is -# set to 'noeviction', Redis will start to reply with errors to commands -# that would use more memory, like SET, LPUSH, and so on, and will continue -# to reply to read-only commands like GET. -# -# This option is usually useful when using Redis as an LRU or LFU cache, or to -# set a hard memory limit for an instance (using the 'noeviction' policy). -# -# WARNING: If you have replicas attached to an instance with maxmemory on, -# the size of the output buffers needed to feed the replicas are subtracted -# from the used memory count, so that network problems / resyncs will -# not trigger a loop where keys are evicted, and in turn the output -# buffer of replicas is full with DELs of keys evicted triggering the deletion -# of more keys, and so forth until the database is completely emptied. -# -# In short... if you have replicas attached it is suggested that you set a lower -# limit for maxmemory so that there is some free RAM on the system for replica -# output buffers (but this is not needed if the policy is 'noeviction'). -# -# maxmemory - -# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory -# is reached. You can select one from the following behaviors: -# -# volatile-lru -> Evict using approximated LRU, only keys with an expire set. -# allkeys-lru -> Evict any key using approximated LRU. -# volatile-lfu -> Evict using approximated LFU, only keys with an expire set. -# allkeys-lfu -> Evict any key using approximated LFU. -# volatile-random -> Remove a random key having an expire set. -# allkeys-random -> Remove a random key, any key. -# volatile-ttl -> Remove the key with the nearest expire time (minor TTL) -# noeviction -> Don't evict anything, just return an error on write operations. -# -# LRU means Least Recently Used -# LFU means Least Frequently Used -# -# Both LRU, LFU and volatile-ttl are implemented using approximated -# randomized algorithms. -# -# Note: with any of the above policies, Redis will return an error on write -# operations, when there are no suitable keys for eviction. -# -# At the date of writing these commands are: set setnx setex append -# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd -# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby -# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby -# getset mset msetnx exec sort -# -# The default is: -# -# maxmemory-policy noeviction - -# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated -# algorithms (in order to save memory), so you can tune it for speed or -# accuracy. By default Redis will check five keys and pick the one that was -# used least recently, you can change the sample size using the following -# configuration directive. -# -# The default of 5 produces good enough results. 10 Approximates very closely -# true LRU but costs more CPU. 3 is faster but not very accurate. -# -# maxmemory-samples 5 - -# Starting from Redis 5, by default a replica will ignore its maxmemory setting -# (unless it is promoted to master after a failover or manually). It means -# that the eviction of keys will be just handled by the master, sending the -# DEL commands to the replica as keys evict in the master side. -# -# This behavior ensures that masters and replicas stay consistent, and is usually -# what you want, however if your replica is writable, or you want the replica -# to have a different memory setting, and you are sure all the writes performed -# to the replica are idempotent, then you may change this default (but be sure -# to understand what you are doing). -# -# Note that since the replica by default does not evict, it may end using more -# memory than the one set via maxmemory (there are certain buffers that may -# be larger on the replica, or data structures may sometimes take more memory -# and so forth). So make sure you monitor your replicas and make sure they -# have enough memory to never hit a real out-of-memory condition before the -# master hits the configured maxmemory setting. -# -# replica-ignore-maxmemory yes - -# Redis reclaims expired keys in two ways: upon access when those keys are -# found to be expired, and also in background, in what is called the -# "active expire key". The key space is slowly and interactively scanned -# looking for expired keys to reclaim, so that it is possible to free memory -# of keys that are expired and will never be accessed again in a short time. -# -# The default effort of the expire cycle will try to avoid having more than -# ten percent of expired keys still in memory, and will try to avoid consuming -# more than 25% of total memory and to add latency to the system. However -# it is possible to increase the expire "effort" that is normally set to -# "1", to a greater value, up to the value "10". At its maximum value the -# system will use more CPU, longer cycles (and technically may introduce -# more latency), and will tolerate less already expired keys still present -# in the system. It's a tradeoff between memory, CPU and latency. -# -# active-expire-effort 1 - -############################# LAZY FREEING #################################### - -# Redis has two primitives to delete keys. One is called DEL and is a blocking -# deletion of the object. It means that the server stops processing new commands -# in order to reclaim all the memory associated with an object in a synchronous -# way. If the key deleted is associated with a small object, the time needed -# in order to execute the DEL command is very small and comparable to most other -# O(1) or O(log_N) commands in Redis. However if the key is associated with an -# aggregated value containing millions of elements, the server can block for -# a long time (even seconds) in order to complete the operation. -# -# For the above reasons Redis also offers non blocking deletion primitives -# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and -# FLUSHDB commands, in order to reclaim memory in background. Those commands -# are executed in constant time. Another thread will incrementally free the -# object in the background as fast as possible. -# -# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. -# It's up to the design of the application to understand when it is a good -# idea to use one or the other. However the Redis server sometimes has to -# delete keys or flush the whole database as a side effect of other operations. -# Specifically Redis deletes objects independently of a user call in the -# following scenarios: -# -# 1) On eviction, because of the maxmemory and maxmemory policy configurations, -# in order to make room for new data, without going over the specified -# memory limit. -# 2) Because of expire: when a key with an associated time to live (see the -# EXPIRE command) must be deleted from memory. -# 3) Because of a side effect of a command that stores data on a key that may -# already exist. For example the RENAME command may delete the old key -# content when it is replaced with another one. Similarly SUNIONSTORE -# or SORT with STORE option may delete existing keys. The SET command -# itself removes any old content of the specified key in order to replace -# it with the specified string. -# 4) During replication, when a replica performs a full resynchronization with -# its master, the content of the whole database is removed in order to -# load the RDB file just transferred. -# -# In all the above cases the default is to delete objects in a blocking way, -# like if DEL was called. However you can configure each case specifically -# in order to instead release memory in a non-blocking way like if UNLINK -# was called, using the following configuration directives. - -lazyfree-lazy-eviction no -lazyfree-lazy-expire no -lazyfree-lazy-server-del no -replica-lazy-flush no - -# It is also possible, for the case when to replace the user code DEL calls -# with UNLINK calls is not easy, to modify the default behavior of the DEL -# command to act exactly like UNLINK, using the following configuration -# directive: - -lazyfree-lazy-user-del no - -################################ THREADED I/O ################################# - -# Redis is mostly single threaded, however there are certain threaded -# operations such as UNLINK, slow I/O accesses and other things that are -# performed on side threads. -# -# Now it is also possible to handle Redis clients socket reads and writes -# in different I/O threads. Since especially writing is so slow, normally -# Redis users use pipelining in order to speed up the Redis performances per -# core, and spawn multiple instances in order to scale more. Using I/O -# threads it is possible to easily speedup two times Redis without resorting -# to pipelining nor sharding of the instance. -# -# By default threading is disabled, we suggest enabling it only in machines -# that have at least 4 or more cores, leaving at least one spare core. -# Using more than 8 threads is unlikely to help much. We also recommend using -# threaded I/O only if you actually have performance problems, with Redis -# instances being able to use a quite big percentage of CPU time, otherwise -# there is no point in using this feature. -# -# So for instance if you have a four cores boxes, try to use 2 or 3 I/O -# threads, if you have a 8 cores, try to use 6 threads. In order to -# enable I/O threads use the following configuration directive: -# -# io-threads 4 -# -# Setting io-threads to 1 will just use the main thread as usual. -# When I/O threads are enabled, we only use threads for writes, that is -# to thread the write(2) syscall and transfer the client buffers to the -# socket. However it is also possible to enable threading of reads and -# protocol parsing using the following configuration directive, by setting -# it to yes: -# -# io-threads-do-reads no -# -# Usually threading reads doesn't help much. -# -# NOTE 1: This configuration directive cannot be changed at runtime via -# CONFIG SET. Aso this feature currently does not work when SSL is -# enabled. -# -# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make -# sure you also run the benchmark itself in threaded mode, using the -# --threads option to match the number of Redis threads, otherwise you'll not -# be able to notice the improvements. - -############################ KERNEL OOM CONTROL ############################## - -# On Linux, it is possible to hint the kernel OOM killer on what processes -# should be killed first when out of memory. -# -# Enabling this feature makes Redis actively control the oom_score_adj value -# for all its processes, depending on their role. The default scores will -# attempt to have background child processes killed before all others, and -# replicas killed before masters. -# -# Redis supports three options: -# -# no: Don't make changes to oom-score-adj (default). -# yes: Alias to "relative" see below. -# absolute: Values in oom-score-adj-values are written as is to the kernel. -# relative: Values are used relative to the initial value of oom_score_adj when -# the server starts and are then clamped to a range of -1000 to 1000. -# Because typically the initial value is 0, they will often match the -# absolute values. -oom-score-adj no - -# When oom-score-adj is used, this directive controls the specific values used -# for master, replica and background child processes. Values range -2000 to -# 2000 (higher means more likely to be killed). -# -# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) -# can freely increase their value, but not decrease it below its initial -# settings. This means that setting oom-score-adj to "relative" and setting the -# oom-score-adj-values to positive values will always succeed. -oom-score-adj-values 0 200 800 - -############################## APPEND ONLY MODE ############################### - -# By default Redis asynchronously dumps the dataset on disk. This mode is -# good enough in many applications, but an issue with the Redis process or -# a power outage may result into a few minutes of writes lost (depending on -# the configured save points). -# -# The Append Only File is an alternative persistence mode that provides -# much better durability. For instance using the default data fsync policy -# (see later in the config file) Redis can lose just one second of writes in a -# dramatic event like a server power outage, or a single write if something -# wrong with the Redis process itself happens, but the operating system is -# still running correctly. -# -# AOF and RDB persistence can be enabled at the same time without problems. -# If the AOF is enabled on startup Redis will load the AOF, that is the file -# with the better durability guarantees. -# -# Please check http://redis.io/topics/persistence for more information. - -appendonly no - -# The name of the append only file (default: "appendonly.aof") - -appendfilename "appendonly.aof" - -# The fsync() call tells the Operating System to actually write data on disk -# instead of waiting for more data in the output buffer. Some OS will really flush -# data on disk, some other OS will just try to do it ASAP. -# -# Redis supports three different modes: -# -# no: don't fsync, just let the OS flush the data when it wants. Faster. -# always: fsync after every write to the append only log. Slow, Safest. -# everysec: fsync only one time every second. Compromise. -# -# The default is "everysec", as that's usually the right compromise between -# speed and data safety. It's up to you to understand if you can relax this to -# "no" that will let the operating system flush the output buffer when -# it wants, for better performances (but if you can live with the idea of -# some data loss consider the default persistence mode that's snapshotting), -# or on the contrary, use "always" that's very slow but a bit safer than -# everysec. -# -# More details please check the following article: -# http://antirez.com/post/redis-persistence-demystified.html -# -# If unsure, use "everysec". - -# appendfsync always -appendfsync everysec -# appendfsync no - -# When the AOF fsync policy is set to always or everysec, and a background -# saving process (a background save or AOF log background rewriting) is -# performing a lot of I/O against the disk, in some Linux configurations -# Redis may block too long on the fsync() call. Note that there is no fix for -# this currently, as even performing fsync in a different thread will block -# our synchronous write(2) call. -# -# In order to mitigate this problem it's possible to use the following option -# that will prevent fsync() from being called in the main process while a -# BGSAVE or BGREWRITEAOF is in progress. -# -# This means that while another child is saving, the durability of Redis is -# the same as "appendfsync none". In practical terms, this means that it is -# possible to lose up to 30 seconds of log in the worst scenario (with the -# default Linux settings). -# -# If you have latency problems turn this to "yes". Otherwise leave it as -# "no" that is the safest pick from the point of view of durability. - -no-appendfsync-on-rewrite no - -# Automatic rewrite of the append only file. -# Redis is able to automatically rewrite the log file implicitly calling -# BGREWRITEAOF when the AOF log size grows by the specified percentage. -# -# This is how it works: Redis remembers the size of the AOF file after the -# latest rewrite (if no rewrite has happened since the restart, the size of -# the AOF at startup is used). -# -# This base size is compared to the current size. If the current size is -# bigger than the specified percentage, the rewrite is triggered. Also -# you need to specify a minimal size for the AOF file to be rewritten, this -# is useful to avoid rewriting the AOF file even if the percentage increase -# is reached but it is still pretty small. -# -# Specify a percentage of zero in order to disable the automatic AOF -# rewrite feature. - -auto-aof-rewrite-percentage 100 -auto-aof-rewrite-min-size 64mb - -# An AOF file may be found to be truncated at the end during the Redis -# startup process, when the AOF data gets loaded back into memory. -# This may happen when the system where Redis is running -# crashes, especially when an ext4 filesystem is mounted without the -# data=ordered option (however this can't happen when Redis itself -# crashes or aborts but the operating system still works correctly). -# -# Redis can either exit with an error when this happens, or load as much -# data as possible (the default now) and start if the AOF file is found -# to be truncated at the end. The following option controls this behavior. -# -# If aof-load-truncated is set to yes, a truncated AOF file is loaded and -# the Redis server starts emitting a log to inform the user of the event. -# Otherwise if the option is set to no, the server aborts with an error -# and refuses to start. When the option is set to no, the user requires -# to fix the AOF file using the "redis-check-aof" utility before to restart -# the server. -# -# Note that if the AOF file will be found to be corrupted in the middle -# the server will still exit with an error. This option only applies when -# Redis will try to read more data from the AOF file but not enough bytes -# will be found. -aof-load-truncated yes - -# When rewriting the AOF file, Redis is able to use an RDB preamble in the -# AOF file for faster rewrites and recoveries. When this option is turned -# on the rewritten AOF file is composed of two different stanzas: -# -# [RDB file][AOF tail] -# -# When loading, Redis recognizes that the AOF file starts with the "REDIS" -# string and loads the prefixed RDB file, then continues loading the AOF -# tail. -aof-use-rdb-preamble yes - -################################ LUA SCRIPTING ############################### - -# Max execution time of a Lua script in milliseconds. -# -# If the maximum execution time is reached Redis will log that a script is -# still in execution after the maximum allowed time and will start to -# reply to queries with an error. -# -# When a long running script exceeds the maximum execution time only the -# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be -# used to stop a script that did not yet call any write commands. The second -# is the only way to shut down the server in the case a write command was -# already issued by the script but the user doesn't want to wait for the natural -# termination of the script. -# -# Set it to 0 or a negative value for unlimited execution without warnings. -lua-time-limit 5000 - -################################ REDIS CLUSTER ############################### - -# Normal Redis instances can't be part of a Redis Cluster; only nodes that are -# started as cluster nodes can. In order to start a Redis instance as a -# cluster node enable the cluster support uncommenting the following: -# -cluster-enabled yes - -# Every cluster node has a cluster configuration file. This file is not -# intended to be edited by hand. It is created and updated by Redis nodes. -# Every Redis Cluster node requires a different cluster configuration file. -# Make sure that instances running in the same system do not have -# overlapping cluster configuration file names. -# -# cluster-config-file /etc/data/nodes.conf - -# Cluster node timeout is the amount of milliseconds a node must be unreachable -# for it to be considered in failure state. -# Most other internal time limits are a multiple of the node timeout. -# -cluster-node-timeout 15000 - -# A replica of a failing master will avoid to start a failover if its data -# looks too old. -# -# There is no simple way for a replica to actually have an exact measure of -# its "data age", so the following two checks are performed: -# -# 1) If there are multiple replicas able to failover, they exchange messages -# in order to try to give an advantage to the replica with the best -# replication offset (more data from the master processed). -# Replicas will try to get their rank by offset, and apply to the start -# of the failover a delay proportional to their rank. -# -# 2) Every single replica computes the time of the last interaction with -# its master. This can be the last ping or command received (if the master -# is still in the "connected" state), or the time that elapsed since the -# disconnection with the master (if the replication link is currently down). -# If the last interaction is too old, the replica will not try to failover -# at all. -# -# The point "2" can be tuned by user. Specifically a replica will not perform -# the failover if, since the last interaction with the master, the time -# elapsed is greater than: -# -# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period -# -# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor -# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the -# replica will not try to failover if it was not able to talk with the master -# for longer than 310 seconds. -# -# A large cluster-replica-validity-factor may allow replicas with too old data to failover -# a master, while a too small value may prevent the cluster from being able to -# elect a replica at all. -# -# For maximum availability, it is possible to set the cluster-replica-validity-factor -# to a value of 0, which means, that replicas will always try to failover the -# master regardless of the last time they interacted with the master. -# (However they'll always try to apply a delay proportional to their -# offset rank). -# -# Zero is the only value able to guarantee that when all the partitions heal -# the cluster will always be able to continue. -# -# cluster-replica-validity-factor 10 - -# Cluster replicas are able to migrate to orphaned masters, that are masters -# that are left without working replicas. This improves the cluster ability -# to resist to failures as otherwise an orphaned master can't be failed over -# in case of failure if it has no working replicas. -# -# Replicas migrate to orphaned masters only if there are still at least a -# given number of other working replicas for their old master. This number -# is the "migration barrier". A migration barrier of 1 means that a replica -# will migrate only if there is at least 1 other working replica for its master -# and so forth. It usually reflects the number of replicas you want for every -# master in your cluster. -# -# Default is 1 (replicas migrate only if their masters remain with at least -# one replica). To disable migration just set it to a very large value. -# A value of 0 can be set but is useful only for debugging and dangerous -# in production. -# -# cluster-migration-barrier 1 - -# By default Redis Cluster nodes stop accepting queries if they detect there -# is at least a hash slot uncovered (no available node is serving it). -# This way if the cluster is partially down (for example a range of hash slots -# are no longer covered) all the cluster becomes, eventually, unavailable. -# It automatically returns available as soon as all the slots are covered again. -# -# However sometimes you want the subset of the cluster which is working, -# to continue to accept queries for the part of the key space that is still -# covered. In order to do so, just set the cluster-require-full-coverage -# option to no. -# -# cluster-require-full-coverage yes - -# This option, when set to yes, prevents replicas from trying to failover its -# master during master failures. However the master can still perform a -# manual failover, if forced to do so. -# -# This is useful in different scenarios, especially in the case of multiple -# data center operations, where we want one side to never be promoted if not -# in the case of a total DC failure. -# -# cluster-replica-no-failover no - -# This option, when set to yes, allows nodes to serve read traffic while the -# the cluster is in a down state, as long as it believes it owns the slots. -# -# This is useful for two cases. The first case is for when an application -# doesn't require consistency of data during node failures or network partitions. -# One example of this is a cache, where as long as the node has the data it -# should be able to serve it. -# -# The second use case is for configurations that don't meet the recommended -# three shards but want to enable cluster mode and scale later. A -# master outage in a 1 or 2 shard configuration causes a read/write outage to the -# entire cluster without this option set, with it set there is only a write outage. -# Without a quorum of masters, slot ownership will not change automatically. -# -# cluster-allow-reads-when-down no - -# In order to setup your cluster make sure to read the documentation -# available at http://redis.io web site. - -########################## CLUSTER DOCKER/NAT support ######################## - -# In certain deployments, Redis Cluster nodes address discovery fails, because -# addresses are NAT-ted or because ports are forwarded (the typical case is -# Docker and other containers). -# -# In order to make Redis Cluster working in such environments, a static -# configuration where each node knows its public address is needed. The -# following two options are used for this scope, and are: -# -# * cluster-announce-ip -# * cluster-announce-port -# * cluster-announce-bus-port -# -# Each instructs the node about its address, client port, and cluster message -# bus port. The information is then published in the header of the bus packets -# so that other nodes will be able to correctly map the address of the node -# publishing the information. -# -# If the above options are not used, the normal Redis Cluster auto-detection -# will be used instead. -# -# Note that when remapped, the bus port may not be at the fixed offset of -# clients port + 10000, so you can specify any port and bus-port depending -# on how they get remapped. If the bus-port is not set, a fixed offset of -# 10000 will be used as usual. -# -# Example: -# -# cluster-announce-ip 10.1.1.5 -# cluster-announce-port 6379 -# cluster-announce-bus-port 6380 - -################################## SLOW LOG ################################### - -# The Redis Slow Log is a system to log queries that exceeded a specified -# execution time. The execution time does not include the I/O operations -# like talking with the client, sending the reply and so forth, -# but just the time needed to actually execute the command (this is the only -# stage of command execution where the thread is blocked and can not serve -# other requests in the meantime). -# -# You can configure the slow log with two parameters: one tells Redis -# what is the execution time, in microseconds, to exceed in order for the -# command to get logged, and the other parameter is the length of the -# slow log. When a new command is logged the oldest one is removed from the -# queue of logged commands. - -# The following time is expressed in microseconds, so 1000000 is equivalent -# to one second. Note that a negative number disables the slow log, while -# a value of zero forces the logging of every command. -slowlog-log-slower-than 10000 - -# There is no limit to this length. Just be aware that it will consume memory. -# You can reclaim memory used by the slow log with SLOWLOG RESET. -slowlog-max-len 128 - -################################ LATENCY MONITOR ############################## - -# The Redis latency monitoring subsystem samples different operations -# at runtime in order to collect data related to possible sources of -# latency of a Redis instance. -# -# Via the LATENCY command this information is available to the user that can -# print graphs and obtain reports. -# -# The system only logs operations that were performed in a time equal or -# greater than the amount of milliseconds specified via the -# latency-monitor-threshold configuration directive. When its value is set -# to zero, the latency monitor is turned off. -# -# By default latency monitoring is disabled since it is mostly not needed -# if you don't have latency issues, and collecting data has a performance -# impact, that while very small, can be measured under big load. Latency -# monitoring can easily be enabled at runtime using the command -# "CONFIG SET latency-monitor-threshold " if needed. -latency-monitor-threshold 0 - -############################# EVENT NOTIFICATION ############################## - -# Redis can notify Pub/Sub clients about events happening in the key space. -# This feature is documented at http://redis.io/topics/notifications -# -# For instance if keyspace events notification is enabled, and a client -# performs a DEL operation on key "foo" stored in the Database 0, two -# messages will be published via Pub/Sub: -# -# PUBLISH __keyspace@0__:foo del -# PUBLISH __keyevent@0__:del foo -# -# It is possible to select the events that Redis will notify among a set -# of classes. Every class is identified by a single character: -# -# K Keyspace events, published with __keyspace@__ prefix. -# E Keyevent events, published with __keyevent@__ prefix. -# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... -# $ String commands -# l List commands -# s Set commands -# h Hash commands -# z Sorted set commands -# x Expired events (events generated every time a key expires) -# e Evicted events (events generated when a key is evicted for maxmemory) -# t Stream commands -# m Key-miss events (Note: It is not included in the 'A' class) -# A Alias for g$lshzxet, so that the "AKE" string means all the events -# (Except key-miss events which are excluded from 'A' due to their -# unique nature). -# -# The "notify-keyspace-events" takes as argument a string that is composed -# of zero or multiple characters. The empty string means that notifications -# are disabled. -# -# Example: to enable list and generic events, from the point of view of the -# event name, use: -# -# notify-keyspace-events Elg -# -# Example 2: to get the stream of the expired keys subscribing to channel -# name __keyevent@0__:expired use: -# -# notify-keyspace-events Ex -# -# By default all notifications are disabled because most users don't need -# this feature and the feature has some overhead. Note that if you don't -# specify at least one of K or E, no events will be delivered. -notify-keyspace-events "" - -############################### GOPHER SERVER ################################# - -# Redis contains an implementation of the Gopher protocol, as specified in -# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt). -# -# The Gopher protocol was very popular in the late '90s. It is an alternative -# to the web, and the implementation both server and client side is so simple -# that the Redis server has just 100 lines of code in order to implement this -# support. -# -# What do you do with Gopher nowadays? Well Gopher never *really* died, and -# lately there is a movement in order for the Gopher more hierarchical content -# composed of just plain text documents to be resurrected. Some want a simpler -# internet, others believe that the mainstream internet became too much -# controlled, and it's cool to create an alternative space for people that -# want a bit of fresh air. -# -# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol -# as a gift. -# -# --- HOW IT WORKS? --- -# -# The Redis Gopher support uses the inline protocol of Redis, and specifically -# two kind of inline requests that were anyway illegal: an empty request -# or any request that starts with "/" (there are no Redis commands starting -# with such a slash). Normal RESP2/RESP3 requests are completely out of the -# path of the Gopher protocol implementation and are served as usual as well. -# -# If you open a connection to Redis when Gopher is enabled and send it -# a string like "/foo", if there is a key named "/foo" it is served via the -# Gopher protocol. -# -# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher -# talking), you likely need a script like the following: -# -# https://github.com/antirez/gopher2redis -# -# --- SECURITY WARNING --- -# -# If you plan to put Redis on the internet in a publicly accessible address -# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance. -# Once a password is set: -# -# 1. The Gopher server (when enabled, not by default) will still serve -# content via Gopher. -# 2. However other commands cannot be called before the client will -# authenticate. -# -# So use the 'requirepass' option to protect your instance. -# -# Note that Gopher is not currently supported when 'io-threads-do-reads' -# is enabled. -# -# To enable Gopher support, uncomment the following line and set the option -# from no (the default) to yes. -# -# gopher-enabled no - -############################### ADVANCED CONFIG ############################### - -# Hashes are encoded using a memory efficient data structure when they have a -# small number of entries, and the biggest entry does not exceed a given -# threshold. These thresholds can be configured using the following directives. -hash-max-ziplist-entries 512 -hash-max-ziplist-value 64 - -# Lists are also encoded in a special way to save a lot of space. -# The number of entries allowed per internal list node can be specified -# as a fixed maximum size or a maximum number of elements. -# For a fixed maximum size, use -5 through -1, meaning: -# -5: max size: 64 Kb <-- not recommended for normal workloads -# -4: max size: 32 Kb <-- not recommended -# -3: max size: 16 Kb <-- probably not recommended -# -2: max size: 8 Kb <-- good -# -1: max size: 4 Kb <-- good -# Positive numbers mean store up to _exactly_ that number of elements -# per list node. -# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), -# but if your use case is unique, adjust the settings as necessary. -list-max-ziplist-size -2 - -# Lists may also be compressed. -# Compress depth is the number of quicklist ziplist nodes from *each* side of -# the list to *exclude* from compression. The head and tail of the list -# are always uncompressed for fast push/pop operations. Settings are: -# 0: disable all list compression -# 1: depth 1 means "don't start compressing until after 1 node into the list, -# going from either the head or tail" -# So: [head]->node->node->...->node->[tail] -# [head], [tail] will always be uncompressed; inner nodes will compress. -# 2: [head]->[next]->node->node->...->node->[prev]->[tail] -# 2 here means: don't compress head or head->next or tail->prev or tail, -# but compress all nodes between them. -# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] -# etc. -list-compress-depth 0 - -# Sets have a special encoding in just one case: when a set is composed -# of just strings that happen to be integers in radix 10 in the range -# of 64 bit signed integers. -# The following configuration setting sets the limit in the size of the -# set in order to use this special memory saving encoding. -set-max-intset-entries 512 - -# Similarly to hashes and lists, sorted sets are also specially encoded in -# order to save a lot of space. This encoding is only used when the length and -# elements of a sorted set are below the following limits: -zset-max-ziplist-entries 128 -zset-max-ziplist-value 64 - -# HyperLogLog sparse representation bytes limit. The limit includes the -# 16 bytes header. When an HyperLogLog using the sparse representation crosses -# this limit, it is converted into the dense representation. -# -# A value greater than 16000 is totally useless, since at that point the -# dense representation is more memory efficient. -# -# The suggested value is ~ 3000 in order to have the benefits of -# the space efficient encoding without slowing down too much PFADD, -# which is O(N) with the sparse encoding. The value can be raised to -# ~ 10000 when CPU is not a concern, but space is, and the data set is -# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. -hll-sparse-max-bytes 3000 - -# Streams macro node max size / items. The stream data structure is a radix -# tree of big nodes that encode multiple items inside. Using this configuration -# it is possible to configure how big a single node can be in bytes, and the -# maximum number of items it may contain before switching to a new node when -# appending new stream entries. If any of the following settings are set to -# zero, the limit is ignored, so for instance it is possible to set just a -# max entires limit by setting max-bytes to 0 and max-entries to the desired -# value. -stream-node-max-bytes 4096 -stream-node-max-entries 100 - -# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in -# order to help rehashing the main Redis hash table (the one mapping top-level -# keys to values). The hash table implementation Redis uses (see dict.c) -# performs a lazy rehashing: the more operation you run into a hash table -# that is rehashing, the more rehashing "steps" are performed, so if the -# server is idle the rehashing is never complete and some more memory is used -# by the hash table. -# -# The default is to use this millisecond 10 times every second in order to -# actively rehash the main dictionaries, freeing memory when possible. -# -# If unsure: -# use "activerehashing no" if you have hard latency requirements and it is -# not a good thing in your environment that Redis can reply from time to time -# to queries with 2 milliseconds delay. -# -# use "activerehashing yes" if you don't have such hard requirements but -# want to free memory asap when possible. -activerehashing yes - -# The client output buffer limits can be used to force disconnection of clients -# that are not reading data from the server fast enough for some reason (a -# common reason is that a Pub/Sub client can't consume messages as fast as the -# publisher can produce them). -# -# The limit can be set differently for the three different classes of clients: -# -# normal -> normal clients including MONITOR clients -# replica -> replica clients -# pubsub -> clients subscribed to at least one pubsub channel or pattern -# -# The syntax of every client-output-buffer-limit directive is the following: -# -# client-output-buffer-limit -# -# A client is immediately disconnected once the hard limit is reached, or if -# the soft limit is reached and remains reached for the specified number of -# seconds (continuously). -# So for instance if the hard limit is 32 megabytes and the soft limit is -# 16 megabytes / 10 seconds, the client will get disconnected immediately -# if the size of the output buffers reach 32 megabytes, but will also get -# disconnected if the client reaches 16 megabytes and continuously overcomes -# the limit for 10 seconds. -# -# By default normal clients are not limited because they don't receive data -# without asking (in a push way), but just after a request, so only -# asynchronous clients may create a scenario where data is requested faster -# than it can read. -# -# Instead there is a default limit for pubsub and replica clients, since -# subscribers and replicas receive data in a push fashion. -# -# Both the hard or the soft limit can be disabled by setting them to zero. -client-output-buffer-limit normal 0 0 0 -client-output-buffer-limit replica 256mb 64mb 60 -client-output-buffer-limit pubsub 32mb 8mb 60 - -# Client query buffers accumulate new commands. They are limited to a fixed -# amount by default in order to avoid that a protocol desynchronization (for -# instance due to a bug in the client) will lead to unbound memory usage in -# the query buffer. However you can configure it here if you have very special -# needs, such us huge multi/exec requests or alike. -# -# client-query-buffer-limit 1gb - -# In the Redis protocol, bulk requests, that are, elements representing single -# strings, are normally limited to 512 mb. However you can change this limit -# here, but must be 1mb or greater -# -# proto-max-bulk-len 512mb - -# Redis calls an internal function to perform many background tasks, like -# closing connections of clients in timeout, purging expired keys that are -# never requested, and so forth. -# -# Not all tasks are performed with the same frequency, but Redis checks for -# tasks to perform according to the specified "hz" value. -# -# By default "hz" is set to 10. Raising the value will use more CPU when -# Redis is idle, but at the same time will make Redis more responsive when -# there are many keys expiring at the same time, and timeouts may be -# handled with more precision. -# -# The range is between 1 and 500, however a value over 100 is usually not -# a good idea. Most users should use the default of 10 and raise this up to -# 100 only in environments where very low latency is required. -hz 10 - -# Normally it is useful to have an HZ value which is proportional to the -# number of clients connected. This is useful in order, for instance, to -# avoid too many clients are processed for each background task invocation -# in order to avoid latency spikes. -# -# Since the default HZ value by default is conservatively set to 10, Redis -# offers, and enables by default, the ability to use an adaptive HZ value -# which will temporarily raise when there are many connected clients. -# -# When dynamic HZ is enabled, the actual configured HZ will be used -# as a baseline, but multiples of the configured HZ value will be actually -# used as needed once more clients are connected. In this way an idle -# instance will use very little CPU time while a busy instance will be -# more responsive. -dynamic-hz yes - -# When a child rewrites the AOF file, if the following option is enabled -# the file will be fsync-ed every 32 MB of data generated. This is useful -# in order to commit the file to the disk more incrementally and avoid -# big latency spikes. -aof-rewrite-incremental-fsync yes - -# When redis saves RDB file, if the following option is enabled -# the file will be fsync-ed every 32 MB of data generated. This is useful -# in order to commit the file to the disk more incrementally and avoid -# big latency spikes. -rdb-save-incremental-fsync yes - -# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good -# idea to start with the default settings and only change them after investigating -# how to improve the performances and how the keys LFU change over time, which -# is possible to inspect via the OBJECT FREQ command. -# -# There are two tunable parameters in the Redis LFU implementation: the -# counter logarithm factor and the counter decay time. It is important to -# understand what the two parameters mean before changing them. -# -# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis -# uses a probabilistic increment with logarithmic behavior. Given the value -# of the old counter, when a key is accessed, the counter is incremented in -# this way: -# -# 1. A random number R between 0 and 1 is extracted. -# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). -# 3. The counter is incremented only if R < P. -# -# The default lfu-log-factor is 10. This is a table of how the frequency -# counter changes with a different number of accesses with different -# logarithmic factors: -# -# +--------+------------+------------+------------+------------+------------+ -# | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | -# +--------+------------+------------+------------+------------+------------+ -# | 0 | 104 | 255 | 255 | 255 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# | 1 | 18 | 49 | 255 | 255 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# | 10 | 10 | 18 | 142 | 255 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# | 100 | 8 | 11 | 49 | 143 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# -# NOTE: The above table was obtained by running the following commands: -# -# redis-benchmark -n 1000000 incr foo -# redis-cli object freq foo -# -# NOTE 2: The counter initial value is 5 in order to give new objects a chance -# to accumulate hits. -# -# The counter decay time is the time, in minutes, that must elapse in order -# for the key counter to be divided by two (or decremented if it has a value -# less <= 10). -# -# The default value for the lfu-decay-time is 1. A special value of 0 means to -# decay the counter every time it happens to be scanned. -# -# lfu-log-factor 10 -# lfu-decay-time 1 - -########################### ACTIVE DEFRAGMENTATION ####################### -# -# What is active defragmentation? -# ------------------------------- -# -# Active (online) defragmentation allows a Redis server to compact the -# spaces left between small allocations and deallocations of data in memory, -# thus allowing to reclaim back memory. -# -# Fragmentation is a natural process that happens with every allocator (but -# less so with Jemalloc, fortunately) and certain workloads. Normally a server -# restart is needed in order to lower the fragmentation, or at least to flush -# away all the data and create it again. However thanks to this feature -# implemented by Oran Agra for Redis 4.0 this process can happen at runtime -# in a "hot" way, while the server is running. -# -# Basically when the fragmentation is over a certain level (see the -# configuration options below) Redis will start to create new copies of the -# values in contiguous memory regions by exploiting certain specific Jemalloc -# features (in order to understand if an allocation is causing fragmentation -# and to allocate it in a better place), and at the same time, will release the -# old copies of the data. This process, repeated incrementally for all the keys -# will cause the fragmentation to drop back to normal values. -# -# Important things to understand: -# -# 1. This feature is disabled by default, and only works if you compiled Redis -# to use the copy of Jemalloc we ship with the source code of Redis. -# This is the default with Linux builds. -# -# 2. You never need to enable this feature if you don't have fragmentation -# issues. -# -# 3. Once you experience fragmentation, you can enable this feature when -# needed with the command "CONFIG SET activedefrag yes". -# -# The configuration parameters are able to fine tune the behavior of the -# defragmentation process. If you are not sure about what they mean it is -# a good idea to leave the defaults untouched. - -# Enabled active defragmentation -# activedefrag no - -# Minimum amount of fragmentation waste to start active defrag -# active-defrag-ignore-bytes 100mb - -# Minimum percentage of fragmentation to start active defrag -# active-defrag-threshold-lower 10 - -# Maximum percentage of fragmentation at which we use maximum effort -# active-defrag-threshold-upper 100 - -# Minimal effort for defrag in CPU percentage, to be used when the lower -# threshold is reached -# active-defrag-cycle-min 1 - -# Maximal effort for defrag in CPU percentage, to be used when the upper -# threshold is reached -# active-defrag-cycle-max 25 - -# Maximum number of set/hash/zset/list fields that will be processed from -# the main dictionary scan -# active-defrag-max-scan-fields 1000 - -# Jemalloc background thread for purging will be enabled by default -jemalloc-bg-thread yes - -# It is possible to pin different threads and processes of Redis to specific -# CPUs in your system, in order to maximize the performances of the server. -# This is useful both in order to pin different Redis threads in different -# CPUs, but also in order to make sure that multiple Redis instances running -# in the same host will be pinned to different CPUs. -# -# Normally you can do this using the "taskset" command, however it is also -# possible to this via Redis configuration directly, both in Linux and FreeBSD. -# -# You can pin the server/IO threads, bio threads, aof rewrite child process, and -# the bgsave child process. The syntax to specify the cpu list is the same as -# the taskset command: -# -# Set redis server/io threads to cpu affinity 0,2,4,6: -# server_cpulist 0-7:2 -# -# Set bio threads to cpu affinity 1,3: -# bio_cpulist 1,3 -# -# Set aof rewrite child process to cpu affinity 8,9,10,11: -# aof_rewrite_cpulist 8-11 -# -# Set bgsave child process to cpu affinity 1,10,11 -# bgsave_cpulist 1,10-11 - -# In some cases redis will emit warnings and even refuse to start if it detects -# that the system is in bad state, it is possible to suppress these warnings -# by setting the following config which takes a space delimited list of warnings -# to suppress -# -# ignore-warnings ARM64-COW-BUG diff --git a/tests/playwright/rte/oss-cluster-7/Dockerfile b/tests/playwright/rte/oss-cluster-7/Dockerfile deleted file mode 100644 index 3c17583efd..0000000000 --- a/tests/playwright/rte/oss-cluster-7/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM redislabs/rejson:1.0.8 as rejson - -FROM redislabs/redisearch:2.0.13 as redisearch - -FROM redis:7.0.0 - -COPY redis.conf /etc/redis/ -COPY --from=rejson /usr/lib/redis/modules/rejson.so /etc/redis/modules/ -COPY --from=redisearch /usr/lib/redis/modules/redisearch.so /etc/redis/modules/ - -CMD [ "redis-server", "/etc/redis/redis.conf" ] diff --git a/tests/playwright/rte/oss-cluster-7/cluster-create.sh b/tests/playwright/rte/oss-cluster-7/cluster-create.sh deleted file mode 100644 index 5456f68479..0000000000 --- a/tests/playwright/rte/oss-cluster-7/cluster-create.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -echo 'Try to sleep for a while...' -sleep 25 -echo 'Creating cluster...' -echo "yes" | redis-cli \ - --cluster create \ - 172.31.100.211:6379 \ - 172.31.100.212:6379 \ - 172.31.100.213:6379 \ - --cluster-replicas 0 \ - && redis-server diff --git a/tests/playwright/rte/oss-cluster-7/creator.Dockerfile b/tests/playwright/rte/oss-cluster-7/creator.Dockerfile deleted file mode 100644 index 4a8295321c..0000000000 --- a/tests/playwright/rte/oss-cluster-7/creator.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM redis:7.0.0 - -USER root - -COPY cluster-create.sh ./ - -RUN chmod a+x cluster-create.sh - -CMD ["/bin/sh", "./cluster-create.sh"] diff --git a/tests/playwright/rte/oss-cluster-7/redis.conf b/tests/playwright/rte/oss-cluster-7/redis.conf deleted file mode 100644 index 3733dcdd32..0000000000 --- a/tests/playwright/rte/oss-cluster-7/redis.conf +++ /dev/null @@ -1,1881 +0,0 @@ -# Redis configuration file example. -# -# Note that in order to read the configuration file, Redis must be -# started with the file path as first argument: -# -# ./redis-server /path/to/redis.conf - -# Note on units: when memory size is needed, it is possible to specify -# it in the usual form of 1k 5GB 4M and so forth: -# -# 1k => 1000 bytes -# 1kb => 1024 bytes -# 1m => 1000000 bytes -# 1mb => 1024*1024 bytes -# 1g => 1000000000 bytes -# 1gb => 1024*1024*1024 bytes -# -# units are case insensitive so 1GB 1Gb 1gB are all the same. - -################################## INCLUDES ################################### - -# Include one or more other config files here. This is useful if you -# have a standard template that goes to all Redis servers but also need -# to customize a few per-server settings. Include files can include -# other files, so use this wisely. -# -# Note that option "include" won't be rewritten by command "CONFIG REWRITE" -# from admin or Redis Sentinel. Since Redis always uses the last processed -# line as value of a configuration directive, you'd better put includes -# at the beginning of this file to avoid overwriting config change at runtime. -# -# If instead you are interested in using includes to override configuration -# options, it is better to use include as the last line. -# -# include /path/to/local.conf -# include /path/to/other.conf - -################################## MODULES ##################################### - -# Load modules at startup. If the server is not able to load modules -# it will abort. It is possible to use multiple loadmodule directives. -# -loadmodule /etc/redis/modules/rejson.so -loadmodule /etc/redis/modules/redisearch.so -# loadmodule /path/to/other_module.so - -################################## NETWORK ##################################### - -# By default, if no "bind" configuration directive is specified, Redis listens -# for connections from all available network interfaces on the host machine. -# It is possible to listen to just one or multiple selected interfaces using -# the "bind" configuration directive, followed by one or more IP addresses. -# -# Examples: -# -# bind 192.168.1.100 10.0.0.1 -# bind 127.0.0.1 ::1 -# -# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the -# internet, binding to all the interfaces is dangerous and will expose the -# instance to everybody on the internet. So by default we uncomment the -# following bind directive, that will force Redis to listen only on the -# IPv4 loopback interface address (this means Redis will only be able to -# accept client connections from the same host that it is running on). -# -# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES -# JUST COMMENT OUT THE FOLLOWING LINE. -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# bind 127.0.0.1 - -# Protected mode is a layer of security protection, in order to avoid that -# Redis instances left open on the internet are accessed and exploited. -# -# When protected mode is on and if: -# -# 1) The server is not binding explicitly to a set of addresses using the -# "bind" directive. -# 2) No password is configured. -# -# The server only accepts connections from clients connecting from the -# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain -# sockets. -# -# By default protected mode is enabled. You should disable it only if -# you are sure you want clients from other hosts to connect to Redis -# even if no authentication is configured, nor a specific set of interfaces -# are explicitly listed using the "bind" directive. -# protected-mode yes - -# Accept connections on the specified port, default is 6379 (IANA #815344). -# If port 0 is specified Redis will not listen on a TCP socket. -port 6379 - -# TCP listen() backlog. -# -# In high requests-per-second environments you need a high backlog in order -# to avoid slow clients connection issues. Note that the Linux kernel -# will silently truncate it to the value of /proc/sys/net/core/somaxconn so -# make sure to raise both the value of somaxconn and tcp_max_syn_backlog -# in order to get the desired effect. -tcp-backlog 511 - -# Unix socket. -# -# Specify the path for the Unix socket that will be used to listen for -# incoming connections. There is no default, so Redis will not listen -# on a unix socket when not specified. -# -# unixsocket /tmp/redis.sock -# unixsocketperm 700 - -# Close the connection after a client is idle for N seconds (0 to disable) -timeout 0 - -# TCP keepalive. -# -# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence -# of communication. This is useful for two reasons: -# -# 1) Detect dead peers. -# 2) Force network equipment in the middle to consider the connection to be -# alive. -# -# On Linux, the specified value (in seconds) is the period used to send ACKs. -# Note that to close the connection the double of the time is needed. -# On other kernels the period depends on the kernel configuration. -# -# A reasonable value for this option is 300 seconds, which is the new -# Redis default starting with Redis 3.2.1. -tcp-keepalive 300 - -################################# TLS/SSL ##################################### - -# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration -# directive can be used to define TLS-listening ports. To enable TLS on the -# default port, use: -# -# port 0 -# tls-port 6379 - -# Configure a X.509 certificate and private key to use for authenticating the -# server to connected clients, masters or cluster peers. These files should be -# PEM formatted. -# -# tls-cert-file /etc/redis/redis.crt -# tls-key-file /etc/redis/redis.key - -# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: -# -# tls-dh-params-file redis.dh - -# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL -# clients and peers. Redis requires an explicit configuration of at least one -# of these, and will not implicitly use the system wide configuration. -# -# tls-ca-cert-file /etc/redis/ca.crt -# tls-ca-cert-dir /etc/ssl/certs - -# By default, clients (including replica servers) on a TLS port are required -# to authenticate using valid client side certificates. -# -# If "no" is specified, client certificates are not required and not accepted. -# If "optional" is specified, client certificates are accepted and must be -# valid if provided, but are not required. -# -# tls-auth-clients yes -# tls-auth-clients optional - -# By default, a Redis replica does not attempt to establish a TLS connection -# with its master. -# -# Use the following directive to enable TLS on replication links. -# -# tls-replication yes - -# By default, the Redis Cluster bus uses a plain TCP connection. To enable -# TLS for the bus protocol, use the following directive: -# -# tls-cluster yes - -# Explicitly specify TLS versions to support. Allowed values are case insensitive -# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or -# any combination. To enable only TLSv1.2 and TLSv1.3, use: -# -# tls-protocols "TLSv1.2 TLSv1.3" - -# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information -# about the syntax of this string. -# -# Note: this configuration applies only to <= TLSv1.2. -# -# tls-ciphers DEFAULT:!MEDIUM - -# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more -# information about the syntax of this string, and specifically for TLSv1.3 -# ciphersuites. -# -# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 - -# When choosing a cipher, use the server's preference instead of the client -# preference. By default, the server follows the client's preference. -# -# tls-prefer-server-ciphers yes - -# By default, TLS session caching is enabled to allow faster and less expensive -# reconnections by clients that support it. Use the following directive to disable -# caching. -# -# tls-session-caching no - -# Change the default number of TLS sessions cached. A zero value sets the cache -# to unlimited size. The default size is 20480. -# -# tls-session-cache-size 5000 - -# Change the default timeout of cached TLS sessions. The default timeout is 300 -# seconds. -# -# tls-session-cache-timeout 60 - -################################# GENERAL ##################################### - -# By default Redis does not run as a daemon. Use 'yes' if you need it. -# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. -daemonize no - -# If you run Redis from upstart or systemd, Redis can interact with your -# supervision tree. Options: -# supervised no - no supervision interaction -# supervised upstart - signal upstart by putting Redis into SIGSTOP mode -# requires "expect stop" in your upstart job config -# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET -# supervised auto - detect upstart or systemd method based on -# UPSTART_JOB or NOTIFY_SOCKET environment variables -# Note: these supervision methods only signal "process is ready." -# They do not enable continuous pings back to your supervisor. -supervised no - -# If a pid file is specified, Redis writes it where specified at startup -# and removes it at exit. -# -# When the server runs non daemonized, no pid file is created if none is -# specified in the configuration. When the server is daemonized, the pid file -# is used even if not specified, defaulting to "/var/run/redis.pid". -# -# Creating a pid file is best effort: if Redis is not able to create it -# nothing bad happens, the server will start and run normally. -pidfile /var/run/redis_6379.pid - -# Specify the server verbosity level. -# This can be one of: -# debug (a lot of information, useful for development/testing) -# verbose (many rarely useful info, but not a mess like the debug level) -# notice (moderately verbose, what you want in production probably) -# warning (only very important / critical messages are logged) -loglevel notice - -# Specify the log file name. Also the empty string can be used to force -# Redis to log on the standard output. Note that if you use standard -# output for logging but daemonize, logs will be sent to /dev/null -logfile "" - -# To enable logging to the system logger, just set 'syslog-enabled' to yes, -# and optionally update the other syslog parameters to suit your needs. -# syslog-enabled no - -# Specify the syslog identity. -# syslog-ident redis - -# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. -# syslog-facility local0 - -# Set the number of databases. The default database is DB 0, you can select -# a different one on a per-connection basis using SELECT where -# dbid is a number between 0 and 'databases'-1 -databases 16 - -# By default Redis shows an ASCII art logo only when started to log to the -# standard output and if the standard output is a TTY. Basically this means -# that normally a logo is displayed only in interactive sessions. -# -# However it is possible to force the pre-4.0 behavior and always show a -# ASCII art logo in startup logs by setting the following option to yes. -always-show-logo yes - -################################ SNAPSHOTTING ################################ -# -# Save the DB on disk: -# -# save -# -# Will save the DB if both the given number of seconds and the given -# number of write operations against the DB occurred. -# -# In the example below the behavior will be to save: -# after 900 sec (15 min) if at least 1 key changed -# after 300 sec (5 min) if at least 10 keys changed -# after 60 sec if at least 10000 keys changed -# -# Note: you can disable saving completely by commenting out all "save" lines. -# -# It is also possible to remove all the previously configured save -# points by adding a save directive with a single empty string argument -# like in the following example: -# -# save "" - -save 900 1 -save 300 10 -save 60 10000 - -# By default Redis will stop accepting writes if RDB snapshots are enabled -# (at least one save point) and the latest background save failed. -# This will make the user aware (in a hard way) that data is not persisting -# on disk properly, otherwise chances are that no one will notice and some -# disaster will happen. -# -# If the background saving process will start working again Redis will -# automatically allow writes again. -# -# However if you have setup your proper monitoring of the Redis server -# and persistence, you may want to disable this feature so that Redis will -# continue to work as usual even if there are problems with disk, -# permissions, and so forth. -stop-writes-on-bgsave-error yes - -# Compress string objects using LZF when dump .rdb databases? -# By default compression is enabled as it's almost always a win. -# If you want to save some CPU in the saving child set it to 'no' but -# the dataset will likely be bigger if you have compressible values or keys. -rdbcompression yes - -# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. -# This makes the format more resistant to corruption but there is a performance -# hit to pay (around 10%) when saving and loading RDB files, so you can disable it -# for maximum performances. -# -# RDB files created with checksum disabled have a checksum of zero that will -# tell the loading code to skip the check. -rdbchecksum yes - -# The filename where to dump the DB -dbfilename dump.rdb - -# Remove RDB files used by replication in instances without persistence -# enabled. By default this option is disabled, however there are environments -# where for regulations or other security concerns, RDB files persisted on -# disk by masters in order to feed replicas, or stored on disk by replicas -# in order to load them for the initial synchronization, should be deleted -# ASAP. Note that this option ONLY WORKS in instances that have both AOF -# and RDB persistence disabled, otherwise is completely ignored. -# -# An alternative (and sometimes better) way to obtain the same effect is -# to use diskless replication on both master and replicas instances. However -# in the case of replicas, diskless is not always an option. -# in the case of replicas, diskless is not always an option. -rdb-del-sync-files no - -# The working directory. -# -# The DB will be written inside this directory, with the filename specified -# above using the 'dbfilename' configuration directive. -# -# The Append Only File will also be created inside this directory. -# -# Note that you must specify a directory here, not a file name. -dir ./ - -################################# REPLICATION ################################# - -# Master-Replica replication. Use replicaof to make a Redis instance a copy of -# another Redis server. A few things to understand ASAP about Redis replication. -# -# +------------------+ +---------------+ -# | Master | ---> | Replica | -# | (receive writes) | | (exact copy) | -# +------------------+ +---------------+ -# -# 1) Redis replication is asynchronous, but you can configure a master to -# stop accepting writes if it appears to be not connected with at least -# a given number of replicas. -# 2) Redis replicas are able to perform a partial resynchronization with the -# master if the replication link is lost for a relatively small amount of -# time. You may want to configure the replication backlog size (see the next -# sections of this file) with a sensible value depending on your needs. -# 3) Replication is automatic and does not need user intervention. After a -# network partition replicas automatically try to reconnect to masters -# and resynchronize with them. -# -# replicaof - -# If the master is password protected (using the "requirepass" configuration -# directive below) it is possible to tell the replica to authenticate before -# starting the replication synchronization process, otherwise the master will -# refuse the replica request. -# -# masterauth defaultpass -# -# However this is not enough if you are using Redis ACLs (for Redis version -# 6 or greater), and the default user is not capable of running the PSYNC -# command and/or other commands needed for replication. In this case it's -# better to configure a special user to use with replication, and specify the -# masteruser configuration as such: -# -# masteruser -# -# When masteruser is specified, the replica will authenticate against its -# master using the new AUTH form: AUTH . - -# When a replica loses its connection with the master, or when the replication -# is still in progress, the replica can act in two different ways: -# -# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will -# still reply to client requests, possibly with out of date data, or the -# data set may just be empty if this is the first synchronization. -# -# 2) If replica-serve-stale-data is set to 'no' the replica will reply with -# an error "SYNC with master in progress" to all commands except: -# INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, -# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, -# HOST and LATENCY. -# -replica-serve-stale-data yes - -# You can configure a replica instance to accept writes or not. Writing against -# a replica instance may be useful to store some ephemeral data (because data -# written on a replica will be easily deleted after resync with the master) but -# may also cause problems if clients are writing to it because of a -# misconfiguration. -# -# Since Redis 2.6 by default replicas are read-only. -# -# Note: read only replicas are not designed to be exposed to untrusted clients -# on the internet. It's just a protection layer against misuse of the instance. -# Still a read only replica exports by default all the administrative commands -# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve -# security of read only replicas using 'rename-command' to shadow all the -# administrative / dangerous commands. -replica-read-only yes - -# Replication SYNC strategy: disk or socket. -# -# New replicas and reconnecting replicas that are not able to continue the -# replication process just receiving differences, need to do what is called a -# "full synchronization". An RDB file is transmitted from the master to the -# replicas. -# -# The transmission can happen in two different ways: -# -# 1) Disk-backed: The Redis master creates a new process that writes the RDB -# file on disk. Later the file is transferred by the parent -# process to the replicas incrementally. -# 2) Diskless: The Redis master creates a new process that directly writes the -# RDB file to replica sockets, without touching the disk at all. -# -# With disk-backed replication, while the RDB file is generated, more replicas -# can be queued and served with the RDB file as soon as the current child -# producing the RDB file finishes its work. With diskless replication instead -# once the transfer starts, new replicas arriving will be queued and a new -# transfer will start when the current one terminates. -# -# When diskless replication is used, the master waits a configurable amount of -# time (in seconds) before starting the transfer in the hope that multiple -# replicas will arrive and the transfer can be parallelized. -# -# With slow disks and fast (large bandwidth) networks, diskless replication -# works better. -repl-diskless-sync no - -# When diskless replication is enabled, it is possible to configure the delay -# the server waits in order to spawn the child that transfers the RDB via socket -# to the replicas. -# -# This is important since once the transfer starts, it is not possible to serve -# new replicas arriving, that will be queued for the next RDB transfer, so the -# server waits a delay in order to let more replicas arrive. -# -# The delay is specified in seconds, and by default is 5 seconds. To disable -# it entirely just set it to 0 seconds and the transfer will start ASAP. -repl-diskless-sync-delay 5 - -# ----------------------------------------------------------------------------- -# WARNING: RDB diskless load is experimental. Since in this setup the replica -# does not immediately store an RDB on disk, it may cause data loss during -# failovers. RDB diskless load + Redis modules not handling I/O reads may also -# cause Redis to abort in case of I/O errors during the initial synchronization -# stage with the master. Use only if your do what you are doing. -# ----------------------------------------------------------------------------- -# -# Replica can load the RDB it reads from the replication link directly from the -# socket, or store the RDB to a file and read that file after it was completely -# received from the master. -# -# In many cases the disk is slower than the network, and storing and loading -# the RDB file may increase replication time (and even increase the master's -# Copy on Write memory and salve buffers). -# However, parsing the RDB file directly from the socket may mean that we have -# to flush the contents of the current database before the full rdb was -# received. For this reason we have the following options: -# -# "disabled" - Don't use diskless load (store the rdb file to the disk first) -# "on-empty-db" - Use diskless load only when it is completely safe. -# "swapdb" - Keep a copy of the current db contents in RAM while parsing -# the data directly from the socket. note that this requires -# sufficient memory, if you don't have it, you risk an OOM kill. -repl-diskless-load disabled - -# Replicas send PINGs to server in a predefined interval. It's possible to -# change this interval with the repl_ping_replica_period option. The default -# value is 10 seconds. -# -# repl-ping-replica-period 10 - -# The following option sets the replication timeout for: -# -# 1) Bulk transfer I/O during SYNC, from the point of view of replica. -# 2) Master timeout from the point of view of replicas (data, pings). -# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). -# -# It is important to make sure that this value is greater than the value -# specified for repl-ping-replica-period otherwise a timeout will be detected -# every time there is low traffic between the master and the replica. The default -# value is 60 seconds. -# -# repl-timeout 60 - -# Disable TCP_NODELAY on the replica socket after SYNC? -# -# If you select "yes" Redis will use a smaller number of TCP packets and -# less bandwidth to send data to replicas. But this can add a delay for -# the data to appear on the replica side, up to 40 milliseconds with -# Linux kernels using a default configuration. -# -# If you select "no" the delay for data to appear on the replica side will -# be reduced but more bandwidth will be used for replication. -# -# By default we optimize for low latency, but in very high traffic conditions -# or when the master and replicas are many hops away, turning this to "yes" may -# be a good idea. -repl-disable-tcp-nodelay no - -# Set the replication backlog size. The backlog is a buffer that accumulates -# replica data when replicas are disconnected for some time, so that when a -# replica wants to reconnect again, often a full resync is not needed, but a -# partial resync is enough, just passing the portion of data the replica -# missed while disconnected. -# -# The bigger the replication backlog, the longer the replica can endure the -# disconnect and later be able to perform a partial resynchronization. -# -# The backlog is only allocated if there is at least one replica connected. -# -# repl-backlog-size 1mb - -# After a master has no connected replicas for some time, the backlog will be -# freed. The following option configures the amount of seconds that need to -# elapse, starting from the time the last replica disconnected, for the backlog -# buffer to be freed. -# -# Note that replicas never free the backlog for timeout, since they may be -# promoted to masters later, and should be able to correctly "partially -# resynchronize" with other replicas: hence they should always accumulate backlog. -# -# A value of 0 means to never release the backlog. -# -# repl-backlog-ttl 3600 - -# The replica priority is an integer number published by Redis in the INFO -# output. It is used by Redis Sentinel in order to select a replica to promote -# into a master if the master is no longer working correctly. -# -# A replica with a low priority number is considered better for promotion, so -# for instance if there are three replicas with priority 10, 100, 25 Sentinel -# will pick the one with priority 10, that is the lowest. -# -# However a special priority of 0 marks the replica as not able to perform the -# role of master, so a replica with priority of 0 will never be selected by -# Redis Sentinel for promotion. -# -# By default the priority is 100. -replica-priority 100 - -# It is possible for a master to stop accepting writes if there are less than -# N replicas connected, having a lag less or equal than M seconds. -# -# The N replicas need to be in "online" state. -# -# The lag in seconds, that must be <= the specified value, is calculated from -# the last ping received from the replica, that is usually sent every second. -# -# This option does not GUARANTEE that N replicas will accept the write, but -# will limit the window of exposure for lost writes in case not enough replicas -# are available, to the specified number of seconds. -# -# For example to require at least 3 replicas with a lag <= 10 seconds use: -# -# min-replicas-to-write 3 -# min-replicas-max-lag 10 -# -# Setting one or the other to 0 disables the feature. -# -# By default min-replicas-to-write is set to 0 (feature disabled) and -# min-replicas-max-lag is set to 10. - -# A Redis master is able to list the address and port of the attached -# replicas in different ways. For example the "INFO replication" section -# offers this information, which is used, among other tools, by -# Redis Sentinel in order to discover replica instances. -# Another place where this info is available is in the output of the -# "ROLE" command of a master. -# -# The listed IP address and port normally reported by a replica is -# obtained in the following way: -# -# IP: The address is auto detected by checking the peer address -# of the socket used by the replica to connect with the master. -# -# Port: The port is communicated by the replica during the replication -# handshake, and is normally the port that the replica is using to -# listen for connections. -# -# However when port forwarding or Network Address Translation (NAT) is -# used, the replica may actually be reachable via different IP and port -# pairs. The following two options can be used by a replica in order to -# report to its master a specific set of IP and port, so that both INFO -# and ROLE will report those values. -# -# There is no need to use both the options if you need to override just -# the port or the IP address. -# -# replica-announce-ip 5.5.5.5 -# replica-announce-port 1234 - -############################### KEYS TRACKING ################################# - -# Redis implements server assisted support for client side caching of values. -# This is implemented using an invalidation table that remembers, using -# 16 millions of slots, what clients may have certain subsets of keys. In turn -# this is used in order to send invalidation messages to clients. Please -# check this page to understand more about the feature: -# -# https://redis.io/topics/client-side-caching -# -# When tracking is enabled for a client, all the read only queries are assumed -# to be cached: this will force Redis to store information in the invalidation -# table. When keys are modified, such information is flushed away, and -# invalidation messages are sent to the clients. However if the workload is -# heavily dominated by reads, Redis could use more and more memory in order -# to track the keys fetched by many clients. -# -# For this reason it is possible to configure a maximum fill value for the -# invalidation table. By default it is set to 1M of keys, and once this limit -# is reached, Redis will start to evict keys in the invalidation table -# even if they were not modified, just to reclaim memory: this will in turn -# force the clients to invalidate the cached values. Basically the table -# maximum size is a trade off between the memory you want to spend server -# side to track information about who cached what, and the ability of clients -# to retain cached objects in memory. -# -# If you set the value to 0, it means there are no limits, and Redis will -# retain as many keys as needed in the invalidation table. -# In the "stats" INFO section, you can find information about the number of -# keys in the invalidation table at every given moment. -# -# Note: when key tracking is used in broadcasting mode, no memory is used -# in the server side so this setting is useless. -# -# tracking-table-max-keys 1000000 - -################################## SECURITY ################################### - -# Warning: since Redis is pretty fast, an outside user can try up to -# 1 million passwords per second against a modern box. This means that you -# should use very strong passwords, otherwise they will be very easy to break. -# Note that because the password is really a shared secret between the client -# and the server, and should not be memorized by any human, the password -# can be easily a long string from /dev/urandom or whatever, so by using a -# long and unguessable password no brute force attack will be possible. - -# Redis ACL users are defined in the following format: -# -# user ... acl rules ... -# -# For example: -# -# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 -# -# The special username "default" is used for new connections. If this user -# has the "nopass" rule, then new connections will be immediately authenticated -# as the "default" user without the need of any password provided via the -# AUTH command. Otherwise if the "default" user is not flagged with "nopass" -# the connections will start in not authenticated state, and will require -# AUTH (or the HELLO command AUTH option) in order to be authenticated and -# start to work. -# -# The ACL rules that describe what a user can do are the following: -# -# on Enable the user: it is possible to authenticate as this user. -# off Disable the user: it's no longer possible to authenticate -# with this user, however the already authenticated connections -# will still work. -# + Allow the execution of that command -# - Disallow the execution of that command -# +@ Allow the execution of all the commands in such category -# with valid categories are like @admin, @set, @sortedset, ... -# and so forth, see the full list in the server.c file where -# the Redis command table is described and defined. -# The special category @all means all the commands, but currently -# present in the server, and that will be loaded in the future -# via modules. -# +|subcommand Allow a specific subcommand of an otherwise -# disabled command. Note that this form is not -# allowed as negative like -DEBUG|SEGFAULT, but -# only additive starting with "+". -# allcommands Alias for +@all. Note that it implies the ability to execute -# all the future commands loaded via the modules system. -# nocommands Alias for -@all. -# ~ Add a pattern of keys that can be mentioned as part of -# commands. For instance ~* allows all the keys. The pattern -# is a glob-style pattern like the one of KEYS. -# It is possible to specify multiple patterns. -# allkeys Alias for ~* -# resetkeys Flush the list of allowed keys patterns. -# > Add this password to the list of valid password for the user. -# For example >mypass will add "mypass" to the list. -# This directive clears the "nopass" flag (see later). -# < Remove this password from the list of valid passwords. -# nopass All the set passwords of the user are removed, and the user -# is flagged as requiring no password: it means that every -# password will work against this user. If this directive is -# used for the default user, every new connection will be -# immediately authenticated with the default user without -# any explicit AUTH command required. Note that the "resetpass" -# directive will clear this condition. -# resetpass Flush the list of allowed passwords. Moreover removes the -# "nopass" status. After "resetpass" the user has no associated -# passwords and there is no way to authenticate without adding -# some password (or setting it as "nopass" later). -# reset Performs the following actions: resetpass, resetkeys, off, -# -@all. The user returns to the same state it has immediately -# after its creation. -# -# ACL rules can be specified in any order: for instance you can start with -# passwords, then flags, or key patterns. However note that the additive -# and subtractive rules will CHANGE MEANING depending on the ordering. -# For instance see the following example: -# -# user alice on +@all -DEBUG ~* >somepassword -# -# This will allow "alice" to use all the commands with the exception of the -# DEBUG command, since +@all added all the commands to the set of the commands -# alice can use, and later DEBUG was removed. However if we invert the order -# of two ACL rules the result will be different: -# -# user alice on -DEBUG +@all ~* >somepassword -# -# Now DEBUG was removed when alice had yet no commands in the set of allowed -# commands, later all the commands are added, so the user will be able to -# execute everything. -# -# Basically ACL rules are processed left-to-right. -# -# For more information about ACL configuration please refer to -# the Redis web site at https://redis.io/topics/acl - -# ACL LOG -# -# The ACL Log tracks failed commands and authentication events associated -# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked -# by ACLs. The ACL Log is stored in memory. You can reclaim memory with -# ACL LOG RESET. Define the maximum entry length of the ACL Log below. -acllog-max-len 128 - -# Using an external ACL file -# -# Instead of configuring users here in this file, it is possible to use -# a stand-alone file just listing users. The two methods cannot be mixed: -# if you configure users here and at the same time you activate the external -# ACL file, the server will refuse to start. -# -# The format of the external ACL user file is exactly the same as the -# format that is used inside redis.conf to describe users. -# -# aclfile /etc/redis/users.acl - -# aclfile /etc/redis/users.acl - -# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility -# layer on top of the new ACL system. The option effect will be just setting -# the password for the default user. Clients will still authenticate using -# AUTH as usually, or more explicitly with AUTH default -# if they follow the new protocol: both will work. -# -# requirepass somepass - -# Command renaming (DEPRECATED). -# -# ------------------------------------------------------------------------ -# WARNING: avoid using this option if possible. Instead use ACLs to remove -# commands from the default user, and put them only in some admin user you -# create for administrative purposes. -# ------------------------------------------------------------------------ -# -# It is possible to change the name of dangerous commands in a shared -# environment. For instance the CONFIG command may be renamed into something -# hard to guess so that it will still be available for internal-use tools -# but not available for general clients. -# -# Example: -# -# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 -# -# It is also possible to completely kill a command by renaming it into -# an empty string: -# -# rename-command CONFIG "" -# -# Please note that changing the name of commands that are logged into the -# AOF file or transmitted to replicas may cause problems. - -################################### CLIENTS #################################### - -# Set the max number of connected clients at the same time. By default -# this limit is set to 10000 clients, however if the Redis server is not -# able to configure the process file limit to allow for the specified limit -# the max number of allowed clients is set to the current file limit -# minus 32 (as Redis reserves a few file descriptors for internal uses). -# -# Once the limit is reached Redis will close all the new connections sending -# an error 'max number of clients reached'. -# -# IMPORTANT: When Redis Cluster is used, the max number of connections is also -# shared with the cluster bus: every node in the cluster will use two -# connections, one incoming and another outgoing. It is important to size the -# limit accordingly in case of very large clusters. -# -# maxclients 10000 - -############################## MEMORY MANAGEMENT ################################ - -# Set a memory usage limit to the specified amount of bytes. -# When the memory limit is reached Redis will try to remove keys -# according to the eviction policy selected (see maxmemory-policy). -# -# If Redis can't remove keys according to the policy, or if the policy is -# set to 'noeviction', Redis will start to reply with errors to commands -# that would use more memory, like SET, LPUSH, and so on, and will continue -# to reply to read-only commands like GET. -# -# This option is usually useful when using Redis as an LRU or LFU cache, or to -# set a hard memory limit for an instance (using the 'noeviction' policy). -# -# WARNING: If you have replicas attached to an instance with maxmemory on, -# the size of the output buffers needed to feed the replicas are subtracted -# from the used memory count, so that network problems / resyncs will -# not trigger a loop where keys are evicted, and in turn the output -# buffer of replicas is full with DELs of keys evicted triggering the deletion -# of more keys, and so forth until the database is completely emptied. -# -# In short... if you have replicas attached it is suggested that you set a lower -# limit for maxmemory so that there is some free RAM on the system for replica -# output buffers (but this is not needed if the policy is 'noeviction'). -# -# maxmemory - -# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory -# is reached. You can select one from the following behaviors: -# -# volatile-lru -> Evict using approximated LRU, only keys with an expire set. -# allkeys-lru -> Evict any key using approximated LRU. -# volatile-lfu -> Evict using approximated LFU, only keys with an expire set. -# allkeys-lfu -> Evict any key using approximated LFU. -# volatile-random -> Remove a random key having an expire set. -# allkeys-random -> Remove a random key, any key. -# volatile-ttl -> Remove the key with the nearest expire time (minor TTL) -# noeviction -> Don't evict anything, just return an error on write operations. -# -# LRU means Least Recently Used -# LFU means Least Frequently Used -# -# Both LRU, LFU and volatile-ttl are implemented using approximated -# randomized algorithms. -# -# Note: with any of the above policies, Redis will return an error on write -# operations, when there are no suitable keys for eviction. -# -# At the date of writing these commands are: set setnx setex append -# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd -# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby -# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby -# getset mset msetnx exec sort -# -# The default is: -# -# maxmemory-policy noeviction - -# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated -# algorithms (in order to save memory), so you can tune it for speed or -# accuracy. By default Redis will check five keys and pick the one that was -# used least recently, you can change the sample size using the following -# configuration directive. -# -# The default of 5 produces good enough results. 10 Approximates very closely -# true LRU but costs more CPU. 3 is faster but not very accurate. -# -# maxmemory-samples 5 - -# Starting from Redis 5, by default a replica will ignore its maxmemory setting -# (unless it is promoted to master after a failover or manually). It means -# that the eviction of keys will be just handled by the master, sending the -# DEL commands to the replica as keys evict in the master side. -# -# This behavior ensures that masters and replicas stay consistent, and is usually -# what you want, however if your replica is writable, or you want the replica -# to have a different memory setting, and you are sure all the writes performed -# to the replica are idempotent, then you may change this default (but be sure -# to understand what you are doing). -# -# Note that since the replica by default does not evict, it may end using more -# memory than the one set via maxmemory (there are certain buffers that may -# be larger on the replica, or data structures may sometimes take more memory -# and so forth). So make sure you monitor your replicas and make sure they -# have enough memory to never hit a real out-of-memory condition before the -# master hits the configured maxmemory setting. -# -# replica-ignore-maxmemory yes - -# Redis reclaims expired keys in two ways: upon access when those keys are -# found to be expired, and also in background, in what is called the -# "active expire key". The key space is slowly and interactively scanned -# looking for expired keys to reclaim, so that it is possible to free memory -# of keys that are expired and will never be accessed again in a short time. -# -# The default effort of the expire cycle will try to avoid having more than -# ten percent of expired keys still in memory, and will try to avoid consuming -# more than 25% of total memory and to add latency to the system. However -# it is possible to increase the expire "effort" that is normally set to -# "1", to a greater value, up to the value "10". At its maximum value the -# system will use more CPU, longer cycles (and technically may introduce -# more latency), and will tolerate less already expired keys still present -# in the system. It's a tradeoff between memory, CPU and latency. -# -# active-expire-effort 1 - -############################# LAZY FREEING #################################### - -# Redis has two primitives to delete keys. One is called DEL and is a blocking -# deletion of the object. It means that the server stops processing new commands -# in order to reclaim all the memory associated with an object in a synchronous -# way. If the key deleted is associated with a small object, the time needed -# in order to execute the DEL command is very small and comparable to most other -# O(1) or O(log_N) commands in Redis. However if the key is associated with an -# aggregated value containing millions of elements, the server can block for -# a long time (even seconds) in order to complete the operation. -# -# For the above reasons Redis also offers non blocking deletion primitives -# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and -# FLUSHDB commands, in order to reclaim memory in background. Those commands -# are executed in constant time. Another thread will incrementally free the -# object in the background as fast as possible. -# -# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. -# It's up to the design of the application to understand when it is a good -# idea to use one or the other. However the Redis server sometimes has to -# delete keys or flush the whole database as a side effect of other operations. -# Specifically Redis deletes objects independently of a user call in the -# following scenarios: -# -# 1) On eviction, because of the maxmemory and maxmemory policy configurations, -# in order to make room for new data, without going over the specified -# memory limit. -# 2) Because of expire: when a key with an associated time to live (see the -# EXPIRE command) must be deleted from memory. -# 3) Because of a side effect of a command that stores data on a key that may -# already exist. For example the RENAME command may delete the old key -# content when it is replaced with another one. Similarly SUNIONSTORE -# or SORT with STORE option may delete existing keys. The SET command -# itself removes any old content of the specified key in order to replace -# it with the specified string. -# 4) During replication, when a replica performs a full resynchronization with -# its master, the content of the whole database is removed in order to -# load the RDB file just transferred. -# -# In all the above cases the default is to delete objects in a blocking way, -# like if DEL was called. However you can configure each case specifically -# in order to instead release memory in a non-blocking way like if UNLINK -# was called, using the following configuration directives. - -lazyfree-lazy-eviction no -lazyfree-lazy-expire no -lazyfree-lazy-server-del no -replica-lazy-flush no - -# It is also possible, for the case when to replace the user code DEL calls -# with UNLINK calls is not easy, to modify the default behavior of the DEL -# command to act exactly like UNLINK, using the following configuration -# directive: - -lazyfree-lazy-user-del no - -################################ THREADED I/O ################################# - -# Redis is mostly single threaded, however there are certain threaded -# operations such as UNLINK, slow I/O accesses and other things that are -# performed on side threads. -# -# Now it is also possible to handle Redis clients socket reads and writes -# in different I/O threads. Since especially writing is so slow, normally -# Redis users use pipelining in order to speed up the Redis performances per -# core, and spawn multiple instances in order to scale more. Using I/O -# threads it is possible to easily speedup two times Redis without resorting -# to pipelining nor sharding of the instance. -# -# By default threading is disabled, we suggest enabling it only in machines -# that have at least 4 or more cores, leaving at least one spare core. -# Using more than 8 threads is unlikely to help much. We also recommend using -# threaded I/O only if you actually have performance problems, with Redis -# instances being able to use a quite big percentage of CPU time, otherwise -# there is no point in using this feature. -# -# So for instance if you have a four cores boxes, try to use 2 or 3 I/O -# threads, if you have a 8 cores, try to use 6 threads. In order to -# enable I/O threads use the following configuration directive: -# -# io-threads 4 -# -# Setting io-threads to 1 will just use the main thread as usual. -# When I/O threads are enabled, we only use threads for writes, that is -# to thread the write(2) syscall and transfer the client buffers to the -# socket. However it is also possible to enable threading of reads and -# protocol parsing using the following configuration directive, by setting -# it to yes: -# -# io-threads-do-reads no -# -# Usually threading reads doesn't help much. -# -# NOTE 1: This configuration directive cannot be changed at runtime via -# CONFIG SET. Aso this feature currently does not work when SSL is -# enabled. -# -# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make -# sure you also run the benchmark itself in threaded mode, using the -# --threads option to match the number of Redis threads, otherwise you'll not -# be able to notice the improvements. - -############################ KERNEL OOM CONTROL ############################## - -# On Linux, it is possible to hint the kernel OOM killer on what processes -# should be killed first when out of memory. -# -# Enabling this feature makes Redis actively control the oom_score_adj value -# for all its processes, depending on their role. The default scores will -# attempt to have background child processes killed before all others, and -# replicas killed before masters. -# -# Redis supports three options: -# -# no: Don't make changes to oom-score-adj (default). -# yes: Alias to "relative" see below. -# absolute: Values in oom-score-adj-values are written as is to the kernel. -# relative: Values are used relative to the initial value of oom_score_adj when -# the server starts and are then clamped to a range of -1000 to 1000. -# Because typically the initial value is 0, they will often match the -# absolute values. -oom-score-adj no - -# When oom-score-adj is used, this directive controls the specific values used -# for master, replica and background child processes. Values range -2000 to -# 2000 (higher means more likely to be killed). -# -# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) -# can freely increase their value, but not decrease it below its initial -# settings. This means that setting oom-score-adj to "relative" and setting the -# oom-score-adj-values to positive values will always succeed. -oom-score-adj-values 0 200 800 - -############################## APPEND ONLY MODE ############################### - -# By default Redis asynchronously dumps the dataset on disk. This mode is -# good enough in many applications, but an issue with the Redis process or -# a power outage may result into a few minutes of writes lost (depending on -# the configured save points). -# -# The Append Only File is an alternative persistence mode that provides -# much better durability. For instance using the default data fsync policy -# (see later in the config file) Redis can lose just one second of writes in a -# dramatic event like a server power outage, or a single write if something -# wrong with the Redis process itself happens, but the operating system is -# still running correctly. -# -# AOF and RDB persistence can be enabled at the same time without problems. -# If the AOF is enabled on startup Redis will load the AOF, that is the file -# with the better durability guarantees. -# -# Please check http://redis.io/topics/persistence for more information. - -appendonly no - -# The name of the append only file (default: "appendonly.aof") - -appendfilename "appendonly.aof" - -# The fsync() call tells the Operating System to actually write data on disk -# instead of waiting for more data in the output buffer. Some OS will really flush -# data on disk, some other OS will just try to do it ASAP. -# -# Redis supports three different modes: -# -# no: don't fsync, just let the OS flush the data when it wants. Faster. -# always: fsync after every write to the append only log. Slow, Safest. -# everysec: fsync only one time every second. Compromise. -# -# The default is "everysec", as that's usually the right compromise between -# speed and data safety. It's up to you to understand if you can relax this to -# "no" that will let the operating system flush the output buffer when -# it wants, for better performances (but if you can live with the idea of -# some data loss consider the default persistence mode that's snapshotting), -# or on the contrary, use "always" that's very slow but a bit safer than -# everysec. -# -# More details please check the following article: -# http://antirez.com/post/redis-persistence-demystified.html -# -# If unsure, use "everysec". - -# appendfsync always -appendfsync everysec -# appendfsync no - -# When the AOF fsync policy is set to always or everysec, and a background -# saving process (a background save or AOF log background rewriting) is -# performing a lot of I/O against the disk, in some Linux configurations -# Redis may block too long on the fsync() call. Note that there is no fix for -# this currently, as even performing fsync in a different thread will block -# our synchronous write(2) call. -# -# In order to mitigate this problem it's possible to use the following option -# that will prevent fsync() from being called in the main process while a -# BGSAVE or BGREWRITEAOF is in progress. -# -# This means that while another child is saving, the durability of Redis is -# the same as "appendfsync none". In practical terms, this means that it is -# possible to lose up to 30 seconds of log in the worst scenario (with the -# default Linux settings). -# -# If you have latency problems turn this to "yes". Otherwise leave it as -# "no" that is the safest pick from the point of view of durability. - -no-appendfsync-on-rewrite no - -# Automatic rewrite of the append only file. -# Redis is able to automatically rewrite the log file implicitly calling -# BGREWRITEAOF when the AOF log size grows by the specified percentage. -# -# This is how it works: Redis remembers the size of the AOF file after the -# latest rewrite (if no rewrite has happened since the restart, the size of -# the AOF at startup is used). -# -# This base size is compared to the current size. If the current size is -# bigger than the specified percentage, the rewrite is triggered. Also -# you need to specify a minimal size for the AOF file to be rewritten, this -# is useful to avoid rewriting the AOF file even if the percentage increase -# is reached but it is still pretty small. -# -# Specify a percentage of zero in order to disable the automatic AOF -# rewrite feature. - -auto-aof-rewrite-percentage 100 -auto-aof-rewrite-min-size 64mb - -# An AOF file may be found to be truncated at the end during the Redis -# startup process, when the AOF data gets loaded back into memory. -# This may happen when the system where Redis is running -# crashes, especially when an ext4 filesystem is mounted without the -# data=ordered option (however this can't happen when Redis itself -# crashes or aborts but the operating system still works correctly). -# -# Redis can either exit with an error when this happens, or load as much -# data as possible (the default now) and start if the AOF file is found -# to be truncated at the end. The following option controls this behavior. -# -# If aof-load-truncated is set to yes, a truncated AOF file is loaded and -# the Redis server starts emitting a log to inform the user of the event. -# Otherwise if the option is set to no, the server aborts with an error -# and refuses to start. When the option is set to no, the user requires -# to fix the AOF file using the "redis-check-aof" utility before to restart -# the server. -# -# Note that if the AOF file will be found to be corrupted in the middle -# the server will still exit with an error. This option only applies when -# Redis will try to read more data from the AOF file but not enough bytes -# will be found. -aof-load-truncated yes - -# When rewriting the AOF file, Redis is able to use an RDB preamble in the -# AOF file for faster rewrites and recoveries. When this option is turned -# on the rewritten AOF file is composed of two different stanzas: -# -# [RDB file][AOF tail] -# -# When loading, Redis recognizes that the AOF file starts with the "REDIS" -# string and loads the prefixed RDB file, then continues loading the AOF -# tail. -aof-use-rdb-preamble yes - -################################ LUA SCRIPTING ############################### - -# Max execution time of a Lua script in milliseconds. -# -# If the maximum execution time is reached Redis will log that a script is -# still in execution after the maximum allowed time and will start to -# reply to queries with an error. -# -# When a long running script exceeds the maximum execution time only the -# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be -# used to stop a script that did not yet call any write commands. The second -# is the only way to shut down the server in the case a write command was -# already issued by the script but the user doesn't want to wait for the natural -# termination of the script. -# -# Set it to 0 or a negative value for unlimited execution without warnings. -lua-time-limit 5000 - -################################ REDIS CLUSTER ############################### - -# Normal Redis instances can't be part of a Redis Cluster; only nodes that are -# started as cluster nodes can. In order to start a Redis instance as a -# cluster node enable the cluster support uncommenting the following: -# -cluster-enabled yes - -# Every cluster node has a cluster configuration file. This file is not -# intended to be edited by hand. It is created and updated by Redis nodes. -# Every Redis Cluster node requires a different cluster configuration file. -# Make sure that instances running in the same system do not have -# overlapping cluster configuration file names. -# -# cluster-config-file /etc/data/nodes.conf - -# Cluster node timeout is the amount of milliseconds a node must be unreachable -# for it to be considered in failure state. -# Most other internal time limits are a multiple of the node timeout. -# -cluster-node-timeout 15000 - -# A replica of a failing master will avoid to start a failover if its data -# looks too old. -# -# There is no simple way for a replica to actually have an exact measure of -# its "data age", so the following two checks are performed: -# -# 1) If there are multiple replicas able to failover, they exchange messages -# in order to try to give an advantage to the replica with the best -# replication offset (more data from the master processed). -# Replicas will try to get their rank by offset, and apply to the start -# of the failover a delay proportional to their rank. -# -# 2) Every single replica computes the time of the last interaction with -# its master. This can be the last ping or command received (if the master -# is still in the "connected" state), or the time that elapsed since the -# disconnection with the master (if the replication link is currently down). -# If the last interaction is too old, the replica will not try to failover -# at all. -# -# The point "2" can be tuned by user. Specifically a replica will not perform -# the failover if, since the last interaction with the master, the time -# elapsed is greater than: -# -# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period -# -# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor -# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the -# replica will not try to failover if it was not able to talk with the master -# for longer than 310 seconds. -# -# A large cluster-replica-validity-factor may allow replicas with too old data to failover -# a master, while a too small value may prevent the cluster from being able to -# elect a replica at all. -# -# For maximum availability, it is possible to set the cluster-replica-validity-factor -# to a value of 0, which means, that replicas will always try to failover the -# master regardless of the last time they interacted with the master. -# (However they'll always try to apply a delay proportional to their -# offset rank). -# -# Zero is the only value able to guarantee that when all the partitions heal -# the cluster will always be able to continue. -# -# cluster-replica-validity-factor 10 - -# Cluster replicas are able to migrate to orphaned masters, that are masters -# that are left without working replicas. This improves the cluster ability -# to resist to failures as otherwise an orphaned master can't be failed over -# in case of failure if it has no working replicas. -# -# Replicas migrate to orphaned masters only if there are still at least a -# given number of other working replicas for their old master. This number -# is the "migration barrier". A migration barrier of 1 means that a replica -# will migrate only if there is at least 1 other working replica for its master -# and so forth. It usually reflects the number of replicas you want for every -# master in your cluster. -# -# Default is 1 (replicas migrate only if their masters remain with at least -# one replica). To disable migration just set it to a very large value. -# A value of 0 can be set but is useful only for debugging and dangerous -# in production. -# -# cluster-migration-barrier 1 - -# By default Redis Cluster nodes stop accepting queries if they detect there -# is at least a hash slot uncovered (no available node is serving it). -# This way if the cluster is partially down (for example a range of hash slots -# are no longer covered) all the cluster becomes, eventually, unavailable. -# It automatically returns available as soon as all the slots are covered again. -# -# However sometimes you want the subset of the cluster which is working, -# to continue to accept queries for the part of the key space that is still -# covered. In order to do so, just set the cluster-require-full-coverage -# option to no. -# -# cluster-require-full-coverage yes - -# This option, when set to yes, prevents replicas from trying to failover its -# master during master failures. However the master can still perform a -# manual failover, if forced to do so. -# -# This is useful in different scenarios, especially in the case of multiple -# data center operations, where we want one side to never be promoted if not -# in the case of a total DC failure. -# -# cluster-replica-no-failover no - -# This option, when set to yes, allows nodes to serve read traffic while the -# the cluster is in a down state, as long as it believes it owns the slots. -# -# This is useful for two cases. The first case is for when an application -# doesn't require consistency of data during node failures or network partitions. -# One example of this is a cache, where as long as the node has the data it -# should be able to serve it. -# -# The second use case is for configurations that don't meet the recommended -# three shards but want to enable cluster mode and scale later. A -# master outage in a 1 or 2 shard configuration causes a read/write outage to the -# entire cluster without this option set, with it set there is only a write outage. -# Without a quorum of masters, slot ownership will not change automatically. -# -# cluster-allow-reads-when-down no - -# In order to setup your cluster make sure to read the documentation -# available at http://redis.io web site. - -########################## CLUSTER DOCKER/NAT support ######################## - -# In certain deployments, Redis Cluster nodes address discovery fails, because -# addresses are NAT-ted or because ports are forwarded (the typical case is -# Docker and other containers). -# -# In order to make Redis Cluster working in such environments, a static -# configuration where each node knows its public address is needed. The -# following two options are used for this scope, and are: -# -# * cluster-announce-ip -# * cluster-announce-port -# * cluster-announce-bus-port -# -# Each instructs the node about its address, client port, and cluster message -# bus port. The information is then published in the header of the bus packets -# so that other nodes will be able to correctly map the address of the node -# publishing the information. -# -# If the above options are not used, the normal Redis Cluster auto-detection -# will be used instead. -# -# Note that when remapped, the bus port may not be at the fixed offset of -# clients port + 10000, so you can specify any port and bus-port depending -# on how they get remapped. If the bus-port is not set, a fixed offset of -# 10000 will be used as usual. -# -# Example: -# -# cluster-announce-ip 10.1.1.5 -# cluster-announce-port 6379 -# cluster-announce-bus-port 6380 - -################################## SLOW LOG ################################### - -# The Redis Slow Log is a system to log queries that exceeded a specified -# execution time. The execution time does not include the I/O operations -# like talking with the client, sending the reply and so forth, -# but just the time needed to actually execute the command (this is the only -# stage of command execution where the thread is blocked and can not serve -# other requests in the meantime). -# -# You can configure the slow log with two parameters: one tells Redis -# what is the execution time, in microseconds, to exceed in order for the -# command to get logged, and the other parameter is the length of the -# slow log. When a new command is logged the oldest one is removed from the -# queue of logged commands. - -# The following time is expressed in microseconds, so 1000000 is equivalent -# to one second. Note that a negative number disables the slow log, while -# a value of zero forces the logging of every command. -slowlog-log-slower-than 10000 - -# There is no limit to this length. Just be aware that it will consume memory. -# You can reclaim memory used by the slow log with SLOWLOG RESET. -slowlog-max-len 128 - -################################ LATENCY MONITOR ############################## - -# The Redis latency monitoring subsystem samples different operations -# at runtime in order to collect data related to possible sources of -# latency of a Redis instance. -# -# Via the LATENCY command this information is available to the user that can -# print graphs and obtain reports. -# -# The system only logs operations that were performed in a time equal or -# greater than the amount of milliseconds specified via the -# latency-monitor-threshold configuration directive. When its value is set -# to zero, the latency monitor is turned off. -# -# By default latency monitoring is disabled since it is mostly not needed -# if you don't have latency issues, and collecting data has a performance -# impact, that while very small, can be measured under big load. Latency -# monitoring can easily be enabled at runtime using the command -# "CONFIG SET latency-monitor-threshold " if needed. -latency-monitor-threshold 0 - -############################# EVENT NOTIFICATION ############################## - -# Redis can notify Pub/Sub clients about events happening in the key space. -# This feature is documented at http://redis.io/topics/notifications -# -# For instance if keyspace events notification is enabled, and a client -# performs a DEL operation on key "foo" stored in the Database 0, two -# messages will be published via Pub/Sub: -# -# PUBLISH __keyspace@0__:foo del -# PUBLISH __keyevent@0__:del foo -# -# It is possible to select the events that Redis will notify among a set -# of classes. Every class is identified by a single character: -# -# K Keyspace events, published with __keyspace@__ prefix. -# E Keyevent events, published with __keyevent@__ prefix. -# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... -# $ String commands -# l List commands -# s Set commands -# h Hash commands -# z Sorted set commands -# x Expired events (events generated every time a key expires) -# e Evicted events (events generated when a key is evicted for maxmemory) -# t Stream commands -# m Key-miss events (Note: It is not included in the 'A' class) -# A Alias for g$lshzxet, so that the "AKE" string means all the events -# (Except key-miss events which are excluded from 'A' due to their -# unique nature). -# -# The "notify-keyspace-events" takes as argument a string that is composed -# of zero or multiple characters. The empty string means that notifications -# are disabled. -# -# Example: to enable list and generic events, from the point of view of the -# event name, use: -# -# notify-keyspace-events Elg -# -# Example 2: to get the stream of the expired keys subscribing to channel -# name __keyevent@0__:expired use: -# -# notify-keyspace-events Ex -# -# By default all notifications are disabled because most users don't need -# this feature and the feature has some overhead. Note that if you don't -# specify at least one of K or E, no events will be delivered. -notify-keyspace-events "" - -############################### GOPHER SERVER ################################# - -# Redis contains an implementation of the Gopher protocol, as specified in -# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt). -# -# The Gopher protocol was very popular in the late '90s. It is an alternative -# to the web, and the implementation both server and client side is so simple -# that the Redis server has just 100 lines of code in order to implement this -# support. -# -# What do you do with Gopher nowadays? Well Gopher never *really* died, and -# lately there is a movement in order for the Gopher more hierarchical content -# composed of just plain text documents to be resurrected. Some want a simpler -# internet, others believe that the mainstream internet became too much -# controlled, and it's cool to create an alternative space for people that -# want a bit of fresh air. -# -# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol -# as a gift. -# -# --- HOW IT WORKS? --- -# -# The Redis Gopher support uses the inline protocol of Redis, and specifically -# two kind of inline requests that were anyway illegal: an empty request -# or any request that starts with "/" (there are no Redis commands starting -# with such a slash). Normal RESP2/RESP3 requests are completely out of the -# path of the Gopher protocol implementation and are served as usual as well. -# -# If you open a connection to Redis when Gopher is enabled and send it -# a string like "/foo", if there is a key named "/foo" it is served via the -# Gopher protocol. -# -# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher -# talking), you likely need a script like the following: -# -# https://github.com/antirez/gopher2redis -# -# --- SECURITY WARNING --- -# -# If you plan to put Redis on the internet in a publicly accessible address -# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance. -# Once a password is set: -# -# 1. The Gopher server (when enabled, not by default) will still serve -# content via Gopher. -# 2. However other commands cannot be called before the client will -# authenticate. -# -# So use the 'requirepass' option to protect your instance. -# -# Note that Gopher is not currently supported when 'io-threads-do-reads' -# is enabled. -# -# To enable Gopher support, uncomment the following line and set the option -# from no (the default) to yes. -# -# gopher-enabled no - -############################### ADVANCED CONFIG ############################### - -# Hashes are encoded using a memory efficient data structure when they have a -# small number of entries, and the biggest entry does not exceed a given -# threshold. These thresholds can be configured using the following directives. -hash-max-ziplist-entries 512 -hash-max-ziplist-value 64 - -# Lists are also encoded in a special way to save a lot of space. -# The number of entries allowed per internal list node can be specified -# as a fixed maximum size or a maximum number of elements. -# For a fixed maximum size, use -5 through -1, meaning: -# -5: max size: 64 Kb <-- not recommended for normal workloads -# -4: max size: 32 Kb <-- not recommended -# -3: max size: 16 Kb <-- probably not recommended -# -2: max size: 8 Kb <-- good -# -1: max size: 4 Kb <-- good -# Positive numbers mean store up to _exactly_ that number of elements -# per list node. -# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), -# but if your use case is unique, adjust the settings as necessary. -list-max-ziplist-size -2 - -# Lists may also be compressed. -# Compress depth is the number of quicklist ziplist nodes from *each* side of -# the list to *exclude* from compression. The head and tail of the list -# are always uncompressed for fast push/pop operations. Settings are: -# 0: disable all list compression -# 1: depth 1 means "don't start compressing until after 1 node into the list, -# going from either the head or tail" -# So: [head]->node->node->...->node->[tail] -# [head], [tail] will always be uncompressed; inner nodes will compress. -# 2: [head]->[next]->node->node->...->node->[prev]->[tail] -# 2 here means: don't compress head or head->next or tail->prev or tail, -# but compress all nodes between them. -# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] -# etc. -list-compress-depth 0 - -# Sets have a special encoding in just one case: when a set is composed -# of just strings that happen to be integers in radix 10 in the range -# of 64 bit signed integers. -# The following configuration setting sets the limit in the size of the -# set in order to use this special memory saving encoding. -set-max-intset-entries 512 - -# Similarly to hashes and lists, sorted sets are also specially encoded in -# order to save a lot of space. This encoding is only used when the length and -# elements of a sorted set are below the following limits: -zset-max-ziplist-entries 128 -zset-max-ziplist-value 64 - -# HyperLogLog sparse representation bytes limit. The limit includes the -# 16 bytes header. When an HyperLogLog using the sparse representation crosses -# this limit, it is converted into the dense representation. -# -# A value greater than 16000 is totally useless, since at that point the -# dense representation is more memory efficient. -# -# The suggested value is ~ 3000 in order to have the benefits of -# the space efficient encoding without slowing down too much PFADD, -# which is O(N) with the sparse encoding. The value can be raised to -# ~ 10000 when CPU is not a concern, but space is, and the data set is -# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. -hll-sparse-max-bytes 3000 - -# Streams macro node max size / items. The stream data structure is a radix -# tree of big nodes that encode multiple items inside. Using this configuration -# it is possible to configure how big a single node can be in bytes, and the -# maximum number of items it may contain before switching to a new node when -# appending new stream entries. If any of the following settings are set to -# zero, the limit is ignored, so for instance it is possible to set just a -# max entires limit by setting max-bytes to 0 and max-entries to the desired -# value. -stream-node-max-bytes 4096 -stream-node-max-entries 100 - -# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in -# order to help rehashing the main Redis hash table (the one mapping top-level -# keys to values). The hash table implementation Redis uses (see dict.c) -# performs a lazy rehashing: the more operation you run into a hash table -# that is rehashing, the more rehashing "steps" are performed, so if the -# server is idle the rehashing is never complete and some more memory is used -# by the hash table. -# -# The default is to use this millisecond 10 times every second in order to -# actively rehash the main dictionaries, freeing memory when possible. -# -# If unsure: -# use "activerehashing no" if you have hard latency requirements and it is -# not a good thing in your environment that Redis can reply from time to time -# to queries with 2 milliseconds delay. -# -# use "activerehashing yes" if you don't have such hard requirements but -# want to free memory asap when possible. -activerehashing yes - -# The client output buffer limits can be used to force disconnection of clients -# that are not reading data from the server fast enough for some reason (a -# common reason is that a Pub/Sub client can't consume messages as fast as the -# publisher can produce them). -# -# The limit can be set differently for the three different classes of clients: -# -# normal -> normal clients including MONITOR clients -# replica -> replica clients -# pubsub -> clients subscribed to at least one pubsub channel or pattern -# -# The syntax of every client-output-buffer-limit directive is the following: -# -# client-output-buffer-limit -# -# A client is immediately disconnected once the hard limit is reached, or if -# the soft limit is reached and remains reached for the specified number of -# seconds (continuously). -# So for instance if the hard limit is 32 megabytes and the soft limit is -# 16 megabytes / 10 seconds, the client will get disconnected immediately -# if the size of the output buffers reach 32 megabytes, but will also get -# disconnected if the client reaches 16 megabytes and continuously overcomes -# the limit for 10 seconds. -# -# By default normal clients are not limited because they don't receive data -# without asking (in a push way), but just after a request, so only -# asynchronous clients may create a scenario where data is requested faster -# than it can read. -# -# Instead there is a default limit for pubsub and replica clients, since -# subscribers and replicas receive data in a push fashion. -# -# Both the hard or the soft limit can be disabled by setting them to zero. -client-output-buffer-limit normal 0 0 0 -client-output-buffer-limit replica 256mb 64mb 60 -client-output-buffer-limit pubsub 32mb 8mb 60 - -# Client query buffers accumulate new commands. They are limited to a fixed -# amount by default in order to avoid that a protocol desynchronization (for -# instance due to a bug in the client) will lead to unbound memory usage in -# the query buffer. However you can configure it here if you have very special -# needs, such us huge multi/exec requests or alike. -# -# client-query-buffer-limit 1gb - -# In the Redis protocol, bulk requests, that are, elements representing single -# strings, are normally limited to 512 mb. However you can change this limit -# here, but must be 1mb or greater -# -# proto-max-bulk-len 512mb - -# Redis calls an internal function to perform many background tasks, like -# closing connections of clients in timeout, purging expired keys that are -# never requested, and so forth. -# -# Not all tasks are performed with the same frequency, but Redis checks for -# tasks to perform according to the specified "hz" value. -# -# By default "hz" is set to 10. Raising the value will use more CPU when -# Redis is idle, but at the same time will make Redis more responsive when -# there are many keys expiring at the same time, and timeouts may be -# handled with more precision. -# -# The range is between 1 and 500, however a value over 100 is usually not -# a good idea. Most users should use the default of 10 and raise this up to -# 100 only in environments where very low latency is required. -hz 10 - -# Normally it is useful to have an HZ value which is proportional to the -# number of clients connected. This is useful in order, for instance, to -# avoid too many clients are processed for each background task invocation -# in order to avoid latency spikes. -# -# Since the default HZ value by default is conservatively set to 10, Redis -# offers, and enables by default, the ability to use an adaptive HZ value -# which will temporarily raise when there are many connected clients. -# -# When dynamic HZ is enabled, the actual configured HZ will be used -# as a baseline, but multiples of the configured HZ value will be actually -# used as needed once more clients are connected. In this way an idle -# instance will use very little CPU time while a busy instance will be -# more responsive. -dynamic-hz yes - -# When a child rewrites the AOF file, if the following option is enabled -# the file will be fsync-ed every 32 MB of data generated. This is useful -# in order to commit the file to the disk more incrementally and avoid -# big latency spikes. -aof-rewrite-incremental-fsync yes - -# When redis saves RDB file, if the following option is enabled -# the file will be fsync-ed every 32 MB of data generated. This is useful -# in order to commit the file to the disk more incrementally and avoid -# big latency spikes. -rdb-save-incremental-fsync yes - -# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good -# idea to start with the default settings and only change them after investigating -# how to improve the performances and how the keys LFU change over time, which -# is possible to inspect via the OBJECT FREQ command. -# -# There are two tunable parameters in the Redis LFU implementation: the -# counter logarithm factor and the counter decay time. It is important to -# understand what the two parameters mean before changing them. -# -# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis -# uses a probabilistic increment with logarithmic behavior. Given the value -# of the old counter, when a key is accessed, the counter is incremented in -# this way: -# -# 1. A random number R between 0 and 1 is extracted. -# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). -# 3. The counter is incremented only if R < P. -# -# The default lfu-log-factor is 10. This is a table of how the frequency -# counter changes with a different number of accesses with different -# logarithmic factors: -# -# +--------+------------+------------+------------+------------+------------+ -# | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | -# +--------+------------+------------+------------+------------+------------+ -# | 0 | 104 | 255 | 255 | 255 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# | 1 | 18 | 49 | 255 | 255 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# | 10 | 10 | 18 | 142 | 255 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# | 100 | 8 | 11 | 49 | 143 | 255 | -# +--------+------------+------------+------------+------------+------------+ -# -# NOTE: The above table was obtained by running the following commands: -# -# redis-benchmark -n 1000000 incr foo -# redis-cli object freq foo -# -# NOTE 2: The counter initial value is 5 in order to give new objects a chance -# to accumulate hits. -# -# The counter decay time is the time, in minutes, that must elapse in order -# for the key counter to be divided by two (or decremented if it has a value -# less <= 10). -# -# The default value for the lfu-decay-time is 1. A special value of 0 means to -# decay the counter every time it happens to be scanned. -# -# lfu-log-factor 10 -# lfu-decay-time 1 - -########################### ACTIVE DEFRAGMENTATION ####################### -# -# What is active defragmentation? -# ------------------------------- -# -# Active (online) defragmentation allows a Redis server to compact the -# spaces left between small allocations and deallocations of data in memory, -# thus allowing to reclaim back memory. -# -# Fragmentation is a natural process that happens with every allocator (but -# less so with Jemalloc, fortunately) and certain workloads. Normally a server -# restart is needed in order to lower the fragmentation, or at least to flush -# away all the data and create it again. However thanks to this feature -# implemented by Oran Agra for Redis 4.0 this process can happen at runtime -# in a "hot" way, while the server is running. -# -# Basically when the fragmentation is over a certain level (see the -# configuration options below) Redis will start to create new copies of the -# values in contiguous memory regions by exploiting certain specific Jemalloc -# features (in order to understand if an allocation is causing fragmentation -# and to allocate it in a better place), and at the same time, will release the -# old copies of the data. This process, repeated incrementally for all the keys -# will cause the fragmentation to drop back to normal values. -# -# Important things to understand: -# -# 1. This feature is disabled by default, and only works if you compiled Redis -# to use the copy of Jemalloc we ship with the source code of Redis. -# This is the default with Linux builds. -# -# 2. You never need to enable this feature if you don't have fragmentation -# issues. -# -# 3. Once you experience fragmentation, you can enable this feature when -# needed with the command "CONFIG SET activedefrag yes". -# -# The configuration parameters are able to fine tune the behavior of the -# defragmentation process. If you are not sure about what they mean it is -# a good idea to leave the defaults untouched. - -# Enabled active defragmentation -# activedefrag no - -# Minimum amount of fragmentation waste to start active defrag -# active-defrag-ignore-bytes 100mb - -# Minimum percentage of fragmentation to start active defrag -# active-defrag-threshold-lower 10 - -# Maximum percentage of fragmentation at which we use maximum effort -# active-defrag-threshold-upper 100 - -# Minimal effort for defrag in CPU percentage, to be used when the lower -# threshold is reached -# active-defrag-cycle-min 1 - -# Maximal effort for defrag in CPU percentage, to be used when the upper -# threshold is reached -# active-defrag-cycle-max 25 - -# Maximum number of set/hash/zset/list fields that will be processed from -# the main dictionary scan -# active-defrag-max-scan-fields 1000 - -# Jemalloc background thread for purging will be enabled by default -jemalloc-bg-thread yes - -# It is possible to pin different threads and processes of Redis to specific -# CPUs in your system, in order to maximize the performances of the server. -# This is useful both in order to pin different Redis threads in different -# CPUs, but also in order to make sure that multiple Redis instances running -# in the same host will be pinned to different CPUs. -# -# Normally you can do this using the "taskset" command, however it is also -# possible to this via Redis configuration directly, both in Linux and FreeBSD. -# -# You can pin the server/IO threads, bio threads, aof rewrite child process, and -# the bgsave child process. The syntax to specify the cpu list is the same as -# the taskset command: -# -# Set redis server/io threads to cpu affinity 0,2,4,6: -# server_cpulist 0-7:2 -# -# Set bio threads to cpu affinity 1,3: -# bio_cpulist 1,3 -# -# Set aof rewrite child process to cpu affinity 8,9,10,11: -# aof_rewrite_cpulist 8-11 -# -# Set bgsave child process to cpu affinity 1,10,11 -# bgsave_cpulist 1,10-11 - -# In some cases redis will emit warnings and even refuse to start if it detects -# that the system is in bad state, it is possible to suppress these warnings -# by setting the following config which takes a space delimited list of warnings -# to suppress -# -# ignore-warnings ARM64-COW-BUG diff --git a/tests/playwright/rte/oss-sentinel/Dockerfile b/tests/playwright/rte/oss-sentinel/Dockerfile deleted file mode 100644 index 61e4b8b533..0000000000 --- a/tests/playwright/rte/oss-sentinel/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM redis:5 - - -ADD sentinel.conf /etc/redis/sentinel.conf -RUN chown redis:redis /etc/redis/sentinel.conf -ENV SENTINEL_QUORUM 2 -ENV SENTINEL_DOWN_AFTER 5000 -ENV SENTINEL_FAILOVER 10000 -ENV SENTINEL_PORT 26000 -ENV AUTH_PASS password -ENV REQUIREPASS="" -ADD entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/entrypoint.sh -ENTRYPOINT ["entrypoint.sh"] \ No newline at end of file diff --git a/tests/playwright/rte/oss-sentinel/entrypoint.sh b/tests/playwright/rte/oss-sentinel/entrypoint.sh deleted file mode 100644 index 390a969f1b..0000000000 --- a/tests/playwright/rte/oss-sentinel/entrypoint.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -sed -i "s/\$SENTINEL_PORT/$SENTINEL_PORT/g" /etc/redis/sentinel.conf -sed -i "s/\$SENTINEL_QUORUM/$SENTINEL_QUORUM/g" /etc/redis/sentinel.conf -sed -i "s/\$SENTINEL_DOWN_AFTER/$SENTINEL_DOWN_AFTER/g" /etc/redis/sentinel.conf -sed -i "s/\$SENTINEL_FAILOVER/$SENTINEL_FAILOVER/g" /etc/redis/sentinel.conf -sed -i "s/\$AUTH_PASS/$AUTH_PASS/g" /etc/redis/sentinel.conf -sed -i "s/\$REQUIREPASS/$REQUIREPASS/g" /etc/redis/sentinel.conf - -exec docker-entrypoint.sh redis-server /etc/redis/sentinel.conf --sentinel diff --git a/tests/playwright/rte/oss-sentinel/sentinel.conf b/tests/playwright/rte/oss-sentinel/sentinel.conf deleted file mode 100644 index 0eca71c59a..0000000000 --- a/tests/playwright/rte/oss-sentinel/sentinel.conf +++ /dev/null @@ -1,15 +0,0 @@ -port 26379 -dir /tmp -sentinel monitor primary-group-1 oss-sentinel-primary-1 6379 $SENTINEL_QUORUM -sentinel down-after-milliseconds primary-group-1 $SENTINEL_DOWN_AFTER -sentinel parallel-syncs primary-group-1 1 -sentinel failover-timeout primary-group-1 $SENTINEL_FAILOVER -sentinel auth-pass primary-group-1 password - -sentinel monitor primary-group-2 oss-sentinel-primary-2 6379 $SENTINEL_QUORUM -sentinel down-after-milliseconds primary-group-2 $SENTINEL_DOWN_AFTER -sentinel parallel-syncs primary-group-2 1 -sentinel failover-timeout primary-group-2 $SENTINEL_FAILOVER -sentinel auth-pass primary-group-2 password - -requirepass "password" diff --git a/tests/playwright/rte/oss-standalone-big/Dockerfile b/tests/playwright/rte/oss-standalone-big/Dockerfile deleted file mode 100644 index 66b7369068..0000000000 --- a/tests/playwright/rte/oss-standalone-big/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM redislabs/redismod - -ARG TEST_DB_DUMP -ADD $TEST_DB_DUMP /data/ - -ADD entrypoint.sh . -RUN chmod +x entrypoint.sh - -ENTRYPOINT ["sh", "entrypoint.sh", "redis-server"] -CMD ["--loadmodule", "/usr/lib/redis/modules/redisai.so", "--loadmodule", "/usr/lib/redis/modules/redisearch.so", "--loadmodule", "/usr/lib/redis/modules/redisgraph.so", "--loadmodule", "/usr/lib/redis/modules/redistimeseries.so", "--loadmodule", "/usr/lib/redis/modules/rejson.so", "--loadmodule", "/usr/lib/redis/modules/redisbloom.so", "--loadmodule", "/usr/lib/redis/modules/redisgears.so", "Plugin", "/var/opt/redislabs/modules/rg/plugin/gears_python.so"] diff --git a/tests/playwright/rte/oss-standalone-big/entrypoint.sh b/tests/playwright/rte/oss-standalone-big/entrypoint.sh deleted file mode 100644 index de1dcd8155..0000000000 --- a/tests/playwright/rte/oss-standalone-big/entrypoint.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -if [ -e dump.tar.gz ] -then - echo 'Extracting .rdb file...' - tar -zxvf dump.tar.gz -fi - -exec "$@" diff --git a/tests/playwright/rte/oss-standalone-tls/Dockerfile b/tests/playwright/rte/oss-standalone-tls/Dockerfile deleted file mode 100644 index 0cf6ec834f..0000000000 --- a/tests/playwright/rte/oss-standalone-tls/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM bitnami/redis:6.0.8 - -ENV ALLOW_EMPTY_PASSWORD yes - -# TLS options -ENV REDIS_TLS_ENABLED yes -ENV REDIS_TLS_PORT 6379 -ENV REDIS_TLS_CERT_FILE /opt/bitnami/redis/certs/redis.crt -ENV REDIS_TLS_KEY_FILE /opt/bitnami/redis/certs/redis.key -ENV REDIS_TLS_CA_FILE /opt/bitnami/redis/certs/redisCA.crt -ENV REDIS_TLS_AUTH_CLIENTS yes - -COPY --chown=1001 ./certs /opt/bitnami/redis/certs/ diff --git a/tests/playwright/rte/oss-standalone-tls/certs/redis.crt b/tests/playwright/rte/oss-standalone-tls/certs/redis.crt deleted file mode 100644 index 2761116425..0000000000 --- a/tests/playwright/rte/oss-standalone-tls/certs/redis.crt +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEzTCCArWgAwIBAgIUALiX/81ndGTG8UCPzu8r4Ev2IhEwDQYJKoZIhvcNAQEL -BQAwHzELMAkGA1UEBhMCQVUxEDAOBgNVBAMMB2V4YW1wbGUwHhcNMjExMDI4MTMy -NzI3WhcNMzExMDI2MTMyNzI3WjANMQswCQYDVQQGEwJBVTCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBAM4osMW/hBlde+/E20wP9X+zJ0AMD6OtfhqQ5brC -gbVs9mPccZ/8R0fj83YtZfnoEodMZ/7yUTeCCPMdprAezMU1KBf9EpZmTdWhpO3e -kESHcQdsKkqGtyjYF7dDahTmKt4a4aHlPH0DJLltB5HlbVabkzlo+3S8QaNwH5lY -yJTQIqiqVzs9oRLT76nZuJjsym0dNXE42rO3KCniI6kvJDmUzBD8Wc94iDExfy7q -qHyV7b2DCp1w7XP4yrQAFQ6kiVqNcfTTAO4MHNP54V2nZLPdOsUD5BsYY8hu0HDc -/PisZ9ZMcw7LMfpUd3dfA9zefQXPQsWJK20ZCNmdIFtwvIFUpYu/FEF3FWO83zeI -XkVZiuCOnoFvp8JIDvXgzXUBWzvYmxLqVSZAuabqU5pKRswDPLGlZTkHbuP2DiXD -LD5AsTnICpzYkUeERSZKf2qc/nTUk04W/7FUT/75ItVzZvu90mPJlmArB0j4zdAG -KwKo8v/cF1hA1YznhibxcUAA/Q/O3Y6CPQ7C3NeaGKcycgUxWoEY3Leno40ukijd -R0MvsaY7V0/up37fkPtH9rcCkZOGVT5Q4Ww9gVO3yseXVkxbJyzHV1tuwg6yY9wO -LOU2Bbmazkjlkb8a5OyQol2zZNJ0L3lvRWTildtGUTsBkeqI6HAedTTHJkhjHh/P -P0+TAgMBAAGjEzARMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQELBQADggIB -AKn+aH60zdd4ItMOhgd4BIok/u6S4NF0oe4kDwC/EU5C0hbuT6gLAn7ArHMeadue -OuSnhqIPfxNR9WCes9OU1O00WeCuWCE4XfNMMFN5iDFfLxO4Oy5Adt0T74hWbcy6 -h28TdcjrkJEr7HR59G5tQ8TW5gVB4a0WXDw0ob9DSxbFKZU1uZm9L/+MgB/SNCHL -GZSKt75Z/M10b9BTC3OG9swsoWvXEjR2ICiwzk+LxVf5K38faDyBrNJVglrpEUZz -gP60kL73qK0y1/i35UuP0yIJIy48XnDsSByN7eBVsNTGMW3CFLKWA4RVfnEHNUff -vsLHXZFYsUIPnPc5jksFwb/wKAe9JbCrgQPhBYaIYkRGiYt64C48r3boIIVoz9+1 -9Nq0Ik06fCzlI9APq2nzEiVeB7mDyZ692neu32QM6zRkYor+W8uI21YnRJWlOx7+ -x2GIh2EZnEYNvbpbvk/fV5AqkYOu9auRAkcKfME7dJ3Gwndl0YBOjE2DMTv6vIjS -dVuGXQCvlzkRAnPMh5MR5/bSUKVvBryXs9ecAMgoVXBVB+4tGWct5ziL+8qyNtgA -WJ2EWj3xtLlMwwQmLjRsCrZjL4liLJG8Yn8Ehfq1rRJREH2O8uYKCO1fdhuI0Y5S -iBPfqJi6QBHj7i01K9OpNUB7l+xAFLA3cBsegcm2GPoL ------END CERTIFICATE----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/redis.key b/tests/playwright/rte/oss-standalone-tls/certs/redis.key deleted file mode 100644 index fb0777e3ea..0000000000 --- a/tests/playwright/rte/oss-standalone-tls/certs/redis.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDOKLDFv4QZXXvv -xNtMD/V/sydADA+jrX4akOW6woG1bPZj3HGf/EdH4/N2LWX56BKHTGf+8lE3ggjz -HaawHszFNSgX/RKWZk3VoaTt3pBEh3EHbCpKhrco2Be3Q2oU5ireGuGh5Tx9AyS5 -bQeR5W1Wm5M5aPt0vEGjcB+ZWMiU0CKoqlc7PaES0++p2biY7MptHTVxONqztygp -4iOpLyQ5lMwQ/FnPeIgxMX8u6qh8le29gwqdcO1z+Mq0ABUOpIlajXH00wDuDBzT -+eFdp2Sz3TrFA+QbGGPIbtBw3Pz4rGfWTHMOyzH6VHd3XwPc3n0Fz0LFiSttGQjZ -nSBbcLyBVKWLvxRBdxVjvN83iF5FWYrgjp6Bb6fCSA714M11AVs72JsS6lUmQLmm -6lOaSkbMAzyxpWU5B27j9g4lwyw+QLE5yAqc2JFHhEUmSn9qnP501JNOFv+xVE/+ -+SLVc2b7vdJjyZZgKwdI+M3QBisCqPL/3BdYQNWM54Ym8XFAAP0Pzt2Ogj0OwtzX -mhinMnIFMVqBGNy3p6ONLpIo3UdDL7GmO1dP7qd+35D7R/a3ApGThlU+UOFsPYFT -t8rHl1ZMWycsx1dbbsIOsmPcDizlNgW5ms5I5ZG/GuTskKJds2TSdC95b0Vk4pXb -RlE7AZHqiOhwHnU0xyZIYx4fzz9PkwIDAQABAoICAHyc/+0oDHNAnK+bsGrTorNj -2S/Pmox3TChGuXYgKEM/79cA4vWvim6cDQe7/U4Hx1tdBeeHFSyWP06k96Kxm1kA -/pExedDLWfTt1kGqLE4gCGRSL2YI9CGOLRerei3TysmiOgygAeYWxlYG33KC2Ypm -U6F6IbS4LnzaQ19v2R6KiMim3j+CyyAUV2O1pO1bBCjcZPdhRGEpLu/SL3gOdLkR -hiAmSSstUjVaE+SKFvnnrmLFGN996SoWkoAnJJNLRXMk2GMCQCejzrEa8+ymSCqo -aOO5rGHsZjQ7N2dhTNALdmCEqW+hxz3nXKcdGbqiCbQ/Sb8ZYNR7M2xGm85p4Ka9 -0UK4cOM1VJPwz8hotSKAUmXnpbu73CsZi5HyzwZkk4FpcaYCYrCbGVmm1cIKEKI7 -8SN/oqgFdj4Ha9cemnu+RecQZouK+wPWtcILd2kstJl52TV952fVOrnXQDo6XCXB -fbs9IYN1hB6N79xv4L7Jj53hSRMeSNf70Ejkh1FXPOvmvFT12wy5JQdBBR5nnb4a -GEsMpGVe1k3bxjK7K263tLSH0UZ8dMgdSx1E4D1hT1K/gEwTSMOJ0E1R0M6SJmF2 -6TnZ0MbJWx6PbICmyZrd2agfTQrq6CgY1fWLGbQrtnwXtsUR7PiHarydXfs3V8g1 -xHnK1bItOBBMOMcWV93hAoIBAQD2xMceUfBUN0TeWrJ4izD3lUYxmWqjTg7kPcjJ -0VN8v3txGAcyPmbuz5AEzvdFjTispoZNB9QOrmsUVWTQDE5otnei9kHWzqJWYHg4 -USuUuAh8OJGCiepo8zHT3qHDNhKGtOAp5LC8TaOznUFr35zCBCOsvQfRUKrv5IOc -vCFjO07Xly8+M3qK7/UswRQ6480VlE2t1p+VNaORHdTDg2tes3/9owuiNmR/sPT8 -nIoe01LS7qmZoiB1vracaLcBf1Iwd7RvKg7mgFJzmowZUYxyX2YGK5qZ1h74In2X -55+qQnNW0RwPijopTv711pMhMKWl8i3ilcCfoeBXz8zCwFfbAoIBAQDV3wHAO7ic -MYP/Bm5jgIwlix1eOWY/yB+VqdYn2GmC49vTEIlIVlFRq0vZE06iUxs87BIV08zO -4w/iKXd7ktkuhphiEuU2yXA3LQPHpbSOW43RONbf4glFU/DlP/P6fiybbWj6+f7L -7Zbvtz5AW03Y4ZpagJTqOgVdJ0MdLnh9vZj6okGGV1fidKtG6hr7Z/oLhnl9aAZK -4vrvBZ//qz99vEVByiV5kRaJDulu+diBy4n6iBjzjHA5a9e7lY3sUBw3DMgb7kYs -JJPkCPdSxCYq4Ef3z/Eao0tyUuCzyznfCMGJA1gBdTpwDNDCTaXqkjR5nvsdE5k0 -IVQgFPtcOPCpAoIBABujNlni+3OzLPdqWQq/LCDOiyoK8LKRj4FomhBgbWVPXNfx -xPyPmJ+uh4bCV1dm1a4giHIgKlPqnPuOBNh4SF/Z79REmGMiiXP7IfvMu4DQi8K9 -4y4nnCVc93uvN5bRe4mywFhw0IqGd4sqVaVrSfdA124FTdbXng14HnVzbJncjpv+ -xr/ErDjbXy5AAbAGy3VbQsfxfbYMZ+Fc4fNzyJa2q+MQW8EzLlZOz2Frdty09lXB -fSVDzzbgwTsLT1PPmrjq7z50C28teA6ShJZhV8WHgbm3MH2CSb2ov0BAJNXA04Ip -sWbcKF9wBYYrHhddh2/qi9EQzJ4UVzf+ggRd3nkCggEAWcjyWjp4KRJcgJ65jwoz -S7uYS6s7MsGYCOOw5S9kNC/mZDhH+ddK8kdAY1RIqbrL74qHmSQ+kgge7epMn9Mp -W+/jXyDhm1t7wZ4jPRhisXTcF56ODpU9IR65PfTYPyvjHCkVbm+vOPt4ZxB9kNUD -3G3xt9bNLXvILrBB66lLqjYDWAzwBy751Tb3hKDZTPv8rAP7Uttt8NhTUi8BWXsR -/34fcRwlGWEAne9lrlIzQ2IofcXO+8fUgTa17ak+WJvVDINQKvGgAf4lHBFrixKP -l2ZqsC1a4bz1+nuym6hQlkJ9xUBjHNGTA+FNbpTcd5qDbx9/+lf09D6dq45DbBb3 -aQKCAQBrnFYocTm/fIeKo1n1kyF2ULkd6k984ztH8UyluXleSS1ShFFoo/x3vz35 -fsZNUggRnSe7/OBGZYquF/1roVULI1hKh4tbEmW4SWNeTFvwXKdRe6T7NnWSZtS/ -KtamA3lT2wtoEVOvfMo8M0hoFuRWdT2M0i+LKZQdRsq18XPLqdHt1kkSNcnPDERm -4gLQ8zXTf2fHrtZmyM8fc0GuTVwprPFeJkLtSPehkeXSTgb6rpyelX9NBUILwRgP -nw0+cbjFDFKaLnIrMFoVAAn/8DcnbbSt1TZhgNsMxY+GHWPBYW8SUi5nBmQQtmA7 -n3ju44acIPvJ9sWuZruVlWZGFaHm ------END PRIVATE KEY----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt b/tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt deleted file mode 100644 index 796fcb3e05..0000000000 --- a/tests/playwright/rte/oss-standalone-tls/certs/redisCA.crt +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFHzCCAwegAwIBAgIUKeAfHPO6uJBW+s8fY2cWKOc+DfgwDQYJKoZIhvcNAQEL -BQAwHzELMAkGA1UEBhMCQVUxEDAOBgNVBAMMB2V4YW1wbGUwHhcNMjExMDI4MTMy -NzI2WhcNMzExMDI2MTMyNzI2WjAfMQswCQYDVQQGEwJBVTEQMA4GA1UEAwwHZXhh -bXBsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANF9A4aeHru2fX1j -U+Bz9D2supYsMG64f+wXNrFPTMxPS/rdjNcqAWeVCknY7d8EO0uBf64Gm4ufQAPV -boINIdgoso9tGfl5LMaaiYq0aD5CK0wmU38pPbKA2Vr9bkrNIYLUFU6oPI7RJ5fL -Pl/vbvHyaXQKcDd5xxusAu3Ytrylq3WaLNWwhT//WRor4SU2qt9s06PiOgCABY+D -olMXI72gDaehRhnbOVXc6GadlHCsE5GHYJ3WcLLY0rGEdlwphcEG5TRVHGBiHOg/ -J0vsiuhwTLyRqQq5L6eFm33d4aRI9JLY8LlU5ywGiVoNl+fFdQr3ovWw7eObQSbg -BuOJhQBTpEmiPgiOC3kAUUrgT/uGS1x9RX+Wj0sY6zs+qOkfhFAcScXQBeZSLNT9 -RYAjZQOTtTQYVwH8NcF2MlwI3tb3qk2+2Xby4YfTHxp42B8IHkedwfFzrwfUDnNM -Cm3GSVtDGv/j4/7fp0oZZROpd5+h1wRhR/HO08rkpwuobo6xGRrrxqbdlsid3OB4 -Kk92Wl8reccxyr2a/7OlrWk284xpQI/nlU6a8bByJp2eTPYuNJFfJkrqua94YOJy -K4d4tLNbQ4X/5g12unGEHg8/HVNHJjCKiU2Gwxhxm50EqmgdgaboDmf+GuVF0tL1 -kGPbbjSrlt2pS+Tdza9EJfmAzVppAgMBAAGjUzBRMB0GA1UdDgQWBBQWqqhnKa+s -5ToC0TYKlIdfytda/jAfBgNVHSMEGDAWgBQWqqhnKa+s5ToC0TYKlIdfytda/jAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQATnfNftaIalbJmNHrq -7KIxyAAEimiIvci5i1f2X8ZB4XY1TWcf0eQ/+oJW9E2Kj83yGaAqKg1FFP8ugquG -pxzMyljTlQi3cWp58ByaYg2XMEyRY5FtbcdbHHJgdM2PSHsegWN/vfJw3H3J2673 -J6Kc/69IwJcE+aYDbLa/cnnRBn9AKHdtNoRmPH+s162oo8I86LQuG942Hx2CHCjt -ttkLwVBtsBKc5hRaPR+Psx68jS41iHJGHUwHNcPl7llBAQe8kKNg4GHJWT5vh7rd -rw4jAGCsoaE5Ol1HyRDprpdcC4o+eQbhMrjMcFzYduuCx1F96BlSXA2mQe+9lD08 -LzdS35ILmSqCTbtOcdHCmnjWp9fhl5mIrJ+I3G33QPHaHJXBfWGNidxjkibwdVxK -eNAOv4lEMCoVQ0occzNyUluJQUFJyvXtXWFSErRH6b78Gsc/AvPijPbSNuT8hRK9 -GC3yRYltDFXcr4+2lxJyoQoR6Y8399oaJm4U17fOIwlM+iI8dT8x+qsT8brw+5kk -oKu4jz8jfkZrUF8V8hIfAUc08IvMAmDwvIMeAjXFmDBEECxXBGRw2hTcY/53Nfdt -PRWzaL/YtKOy/9UfJQKs3Ihqte59/v3EJO9x9vTLmcpoCgh9piNVgD6OS38cOiEa -snS90+qMig9Gx3aJ+UvktWcp3Q== ------END CERTIFICATE----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/user.crt b/tests/playwright/rte/oss-standalone-tls/certs/user.crt deleted file mode 100644 index ecd9b6f068..0000000000 --- a/tests/playwright/rte/oss-standalone-tls/certs/user.crt +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEzTCCArWgAwIBAgIUALiX/81ndGTG8UCPzu8r4Ev2IhIwDQYJKoZIhvcNAQEL -BQAwHzELMAkGA1UEBhMCQVUxEDAOBgNVBAMMB2V4YW1wbGUwHhcNMjExMDI4MTQz -NTAzWhcNMzExMDI2MTQzNTAzWjANMQswCQYDVQQGEwJBVTCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBAKOod8jpFXqjtNvl0FgIkg0fSZbzvh7jbI7TEUVQ -myeZxjmB3fZh5f6dxM7TZ048CUOeUeq3lemDqay+Moku0rL4PsFNe8z1C1zHuhf9 -4Qw/f7rMBIZ73L4Y/7cPWfjZbeme06+D7HMBZGTWGHZCWrqZQOwA3hKBjC3VY/a5 -z6oP78+w18WDpnavGwXwgCd1yTOwz3tVJUOcJdjGv3iwrHABcGVfxUEKTabP+p6V -HA/+w4AlCloS57GQCh0RWCXMyfekv6MGBaqQa6GtOK5ScLJ1YSlJ6PRoK2N+shbw -L/kQGlilgYBVGOQgNKd94+PwJgOCy72S7p9yF3ZTBB4/51Bwl7IV74Om/GmqzJMx -xY9/PPaxKlOkP+dW41/IrcDULdh0jAfe9rKdFf9/9NWA37S68pKFpzRuRrpLqIwm -BPtHvtLnTbhgmS/O1Rwmxqs8r+VA6D8+/drAor/KAcCwgRiYLvhvl4ABoqj4toEK -jCXAR/jeoLAb8HDBzkot4hhJPjMhQMYX9/HfdK4YX359EkHdsO/+R6+ImXb68DS5 -zh0028ktMM+KEhWSffSmU3imZOrH1/TQfSxfzuTHvyd0HXAHvzx+w1VWNK4fqU8O -tDbMt1GAaatrfrqwP4qTjzLEqtlJLIjg4qgzpYCRUvgVdxyeii9o7IeYT8I6Penf -QpAJAgMBAAGjEzARMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQELBQADggIB -ABb+A9C1AqstP5d2HXS/pIef1BNYAV8A/D2+nUpEXiautmjRZBRNwzBX2ffEZWV7 -EMkvqrRwz2ZgOJ4BRzJiDIYoF8dOhd0lc/0PqoR0xVzjOFDqd0ZcPHAjaY3UoBE7 -jQSQ6ccc1tY5peNLAWCvRO6V9yhdV/SKGhveXGl/24MK9juwArnAitekCWZJQifT -CFOJX5UvifrT8s0v0AqkycaNpkMvl0BAl4DRDJ3+EwZmzfOdATawyXBVXHt1Gz+N -iskPJAJsIjEdFYTjDUzwRN3bHFbTRXt2v1U18YIvMjvxq8MlITEC2lEW+3Xu90d3 -aE/N9mLNJCgmZ2CGywWoaJlUXix2LTo5kT5coVVx0HK0tg5EcBua05qM3xO9Rgxv -HkCnm/jMeN4oQ5o7h+q7UQja8mg1bjCzlt+RxqoA1snjglra/h5I8TTEhvSfxEy7 -h5Wiwne/TH/e8fN1IYRDvv602MNSZnAEPyG3Hc5xQOSGNpoKOZG7tpU+mRYIvlPe -JgA5WNZ83y25JqSxF3kQuk7vrLByzEByqV3j+jIAQiHu/qIXwXUpkoV3L6A18yx/ -TbpQasr/bRFZKe83WlNl2ASAVyubal8ocmA0ua24/RV0I0VOCEXiIkl+pZ6e5Qn4 -L6Tryy5NxaEpUAZ9yv3P75PfNVQ3+vGYi3BLuhZUf/Dd ------END CERTIFICATE----- diff --git a/tests/playwright/rte/oss-standalone-tls/certs/user.key b/tests/playwright/rte/oss-standalone-tls/certs/user.key deleted file mode 100644 index f201473517..0000000000 --- a/tests/playwright/rte/oss-standalone-tls/certs/user.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCjqHfI6RV6o7Tb -5dBYCJINH0mW874e42yO0xFFUJsnmcY5gd32YeX+ncTO02dOPAlDnlHqt5Xpg6ms -vjKJLtKy+D7BTXvM9Qtcx7oX/eEMP3+6zASGe9y+GP+3D1n42W3pntOvg+xzAWRk -1hh2Qlq6mUDsAN4SgYwt1WP2uc+qD+/PsNfFg6Z2rxsF8IAndckzsM97VSVDnCXY -xr94sKxwAXBlX8VBCk2mz/qelRwP/sOAJQpaEuexkAodEVglzMn3pL+jBgWqkGuh -rTiuUnCydWEpSej0aCtjfrIW8C/5EBpYpYGAVRjkIDSnfePj8CYDgsu9ku6fchd2 -UwQeP+dQcJeyFe+DpvxpqsyTMcWPfzz2sSpTpD/nVuNfyK3A1C3YdIwH3vaynRX/ -f/TVgN+0uvKShac0bka6S6iMJgT7R77S5024YJkvztUcJsarPK/lQOg/Pv3awKK/ -ygHAsIEYmC74b5eAAaKo+LaBCowlwEf43qCwG/Bwwc5KLeIYST4zIUDGF/fx33Su -GF9+fRJB3bDv/keviJl2+vA0uc4dNNvJLTDPihIVkn30plN4pmTqx9f00H0sX87k -x78ndB1wB788fsNVVjSuH6lPDrQ2zLdRgGmra366sD+Kk48yxKrZSSyI4OKoM6WA -kVL4FXccnoovaOyHmE/COj3p30KQCQIDAQABAoICADAiwPiq9dJYjD2RXrJF8w9B -AJgRoP3cznVDx3SnvLrtE8yeUfbB3LADH3vl2iC8r8zfqCBtVv6T5zgTyTFoQDi7 -o1mfvKYP/QORCz87QRIlKyB6GWqky8xt9eiV71SuPxHT0Vdyaf15j1nJTvCZm63+ -nYXMy4SN7fkdJoXPKTFP9q0TyqMhkbie0Efy8P6qOj+l5aDU7lzwdIFKE88fx9g5 -1CE9BfuXWDeUPJagLNzXhhEO0/iiTtt/Djp2e4LCtTTNlEAS6V+9kqq/FEjRnqwe -sjE+t/ILIZfmD+OHSdTr05P3OhvQ671Na69H69uDKuslcV+U8/KZ0CTRTgjHqvUZ -eLNC8BZfAk8IZx637/rSlqPmxyS/j35vdslebTbWV2KM7jXPqSb9YokdoJ6M0NZX -IYiMK2reVzjy2YvX1Nhp4Xn68il10XVS4P9tFxyNWdTclCbuSlTfgc27ercQMMgY -fe7/8+A/QhV8tdly8W3HwTmvkmmWRSTMziI+zQzZmYYlAWb33rQYfMoHs4tEf2u2 -Rf0Oso56X73sc3ncnOFm+s5iwTeUH6EgF3ephJX4nR3canmtpy40nbXUJ+tAuaAj -uo56KNlPxIHKf96o2LGXGTrgbH39f0MebWOq/7YjtCg6sUbwuyyG3afLTHHuss13 -5bTJ5gD3rsiGUWjfY3oBAoIBAQDRR/BnDw501Hky32/Vhlt7Ef1iplru+Fh0yQj7 -2DQ+U+L1Ir4Q67ESH8qDnjkyLP1a8BDNOIEEGp5dBb+OHb/rwdb+RZ7OCIzFCQ/d -WR7m0ucuPBQwytQb7iXa9w0umZwoeTXEGP8aGe+bSBIHv8/em26rkSx0A1rxr2/O -1ho8xxgBmOxL3NSCnv56JUu/W0vFq/7OfWQ19SOvFahp3TeqR1gkHe76teWv11Pj -+RdiIIdCOifWChZPEdgMZD4rl1cs9QQb+n+WkRt/mZgtTIRQIe+we+vIha7TW46X -6A1DjSxV4WUSXvv10heYYpZkKzpNG9YOhRB3bvyDkRy11XZ5AoIBAQDIMUETtoa9 -EFNY+uieZwJCTWrrB1njLLRZS3eCAKsVegHD0txLG8H5VMkyZQErRe52zR9QXWU/ -U80tIO5BTbP3ME2AbjJvMwuiEe1lBKlVnn2JSGjbtzUMa1QBvDRmBEZkr8OneMN6 -p2tX3L3Vw8Xm/97rjkAgo3gQkqyDf6VZ4xvH2Wo405yMywcoifMZXo/PN9fI5V8S -fi3XjHrHzaY4cucbdaezVb4Zd0xwl+c6Ifw6+VtmRyfCEHk8yvSkoKWqdxtD0p3a -3e8txYoI/YZltAICZ2vjZPv05Ts/VwWVzaxUArYiUH+k6J+6yCavKWesmeac0vLG -yN07gpRPPsIRAoIBADIp+UDqxf9REsAT+L2I2BK27DKiR3eyhZlwuruLRnKOLv+t -VTu/ExGSFzvXSERzrkMG+jAG1D4El2MaxqCtFtzO+Na4H2mpePydwHTBMPwJH6rg -ccKES7VqLx6+SyWZYmn9K9sWVseN4fYpn1DGNHBad3ueb7ZbO4hlEfrVLTLWUjXH -zxQcGcA5liv3FqIGozH9mTUrr0KTwPrtyRGfGgGx2jnGBwuHYEf26D/j7Cv0Ohew -0u2mO1S2pT/LI2/VderrzBFcyQpxO9MpIOXyymBe0hJOkeTdzlsRPivBTrSbeT4Y -qd5ucByrQEahkwTtq6rh+jw+vwSx0MtElEotoZkCggEAB8ujNRlOdd5E4JokpMZu -GBbbqvtGTMpY24FMzgsonlV57B4x5drW2taqXwP/36eBea7TIVYBs02YF8HIhVJ5 -R47h9bZU0G+0bEM2c1CTJ3pceRQQwT2JG0qyor6pa6+O7izJ+aOCOSx7yZgW7FQL -SMt96r5HUP4MltifTx+RWMa3NjkJId1boz/kr3dvt/UutGsARBpqcVXogxQ9U7p2 -Voxi43bZaOpV1LgIifngTysznzhGjt0Gd1Ac6HkevapjyReKQEHbU8KApc+jaGY2 -7Y7s5RsR4HD2PrsOa5D/7q1roHnajcuErO9CCQvyNa/vEZGMoV61hXgc5UxYah2P -gQKCAQEAkzISMmGPyQT7t6F/P2dFmrotAUU8gsEaWhrlkS0AuREXv1p14I1OnQhS -eWU7I9qSG4NfslRi5WUnowyawQKYibShtJ9/tOWMTaEELVTDtPAIu2y9kcquiG2j -o34vfpByz0w1vhmd/hwcPAvBFV+oaGN6lPz9Pv9MlNBLJoMhCPdr3aBJJuThT1Ka -JQ/RT0XfU7XXSC74x7JwoKB4bobVHdON09yielC6w9wq9anqD18nrz/4wBwWDhDE -KPxeXVpnIZfhukmWxkBY8NLAOFEenS3f6D4wzuOD25mPRSJQTngh7w9XkZYzDnOo -iwa43+YOKJx4Qh4SeXLBc/Udm1eMTA== ------END PRIVATE KEY----- diff --git a/tests/playwright/rte/redis-enterprise/Dockerfile b/tests/playwright/rte/redis-enterprise/Dockerfile deleted file mode 100644 index 8af7097d05..0000000000 --- a/tests/playwright/rte/redis-enterprise/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM redislabs/redis:6.2.8-50 - -## Set the env var to instruct RE to create a cluster on startup -ENV BOOTSTRAP_ACTION create_cluster -ENV BOOTSTRAP_CLUSTER_FQDN cluster.local - -COPY entrypoint.sh db.json ./ - -ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/tests/playwright/rte/redis-enterprise/db.json b/tests/playwright/rte/redis-enterprise/db.json deleted file mode 100644 index 97fe1df45b..0000000000 --- a/tests/playwright/rte/redis-enterprise/db.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "test-re-standalone", - "type": "redis", - "memory_size": 268435456, - "port": 12000 -} diff --git a/tests/playwright/rte/redis-enterprise/entrypoint.sh b/tests/playwright/rte/redis-enterprise/entrypoint.sh deleted file mode 100644 index 70b5c44bdb..0000000000 --- a/tests/playwright/rte/redis-enterprise/entrypoint.sh +++ /dev/null @@ -1,42 +0,0 @@ -#! /bin/bash - -TEST_RE_USER=${TEST_RE_USER:-"demo@redislabs.com"} -TEST_RE_PASS=${TEST_RE_PASS:-"123456"} - -set -e - -# enable job control -set -m - -/opt/start.sh & - -# This command queries the REST API and outputs the status code -CURL_CMD="curl --silent --fail --output /dev/null -i -w %{http_code} -u $TEST_RE_USER:$TEST_RE_PASS -k https://localhost:9443/v1/nodes" - -# Wait to get 2 consecutive 200 responses from the REST API -while true -do - echo yay $CURL_CMD - CURL_CMD_OUTPUT=$($CURL_CMD || true) - if [ $CURL_CMD_OUTPUT == "200" ] - then - echo "Got 200 response, trying again in 5 seconds to verify..." - sleep 5 - if [ $($CURL_CMD || true) == "200" ] - then - echo "Got 200 response after 5 seconds again, proceeding..." - break - fi - else - echo "Did not get 200 response, got $CURL_CMD_OUTPUT, trying again in 10 seconds..." - sleep 10 - fi -done - -echo "Creating databases..." - -curl -k -u "$TEST_RE_USER:$TEST_RE_PASS" --request POST --url "https://localhost:9443/v1/bdbs" --header 'content-type: application/json' --data-binary "@db.json" - -# now we bring the primary process back into the foreground -# and leave it there -fg diff --git a/tests/playwright/rte/ssh/keys/pub/test.pub b/tests/playwright/rte/ssh/keys/pub/test.pub deleted file mode 100644 index 154d1f0e20..0000000000 --- a/tests/playwright/rte/ssh/keys/pub/test.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPXS0xkxY7o+MUNBJJnf6fKh6AFFpzB0YIfifHSSseXw diff --git a/tests/playwright/rte/ssh/keys/pub/testp.pub b/tests/playwright/rte/ssh/keys/pub/testp.pub deleted file mode 100644 index 4412a28b15..0000000000 --- a/tests/playwright/rte/ssh/keys/pub/testp.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEs/ewkUXl0+uDr7hxSM2vURqdRNFHm7+x05azzW/Yzu diff --git a/tests/playwright/rte/ssh/keys/test b/tests/playwright/rte/ssh/keys/test deleted file mode 100644 index 850c7de9e6..0000000000 --- a/tests/playwright/rte/ssh/keys/test +++ /dev/null @@ -1,7 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACD10tMZMWO6PjFDQSSZ3+nyoegBRacwdGCH4nx0krHl8AAAAKBv1saEb9bG -hAAAAAtzc2gtZWQyNTUxOQAAACD10tMZMWO6PjFDQSSZ3+nyoegBRacwdGCH4nx0krHl8A -AAAEDyew1DnmWamAr0OrUM87FauJfFfea+pi8ctpKNnurNi/XS0xkxY7o+MUNBJJnf6fKh -6AFFpzB0YIfifHSSseXwAAAAG3pvem9Aem96by1IUC1Qcm9Cb29rLTQ1MC1HNwEC ------END OPENSSH PRIVATE KEY----- diff --git a/tests/playwright/rte/ssh/keys/testp b/tests/playwright/rte/ssh/keys/testp deleted file mode 100644 index 1da447a570..0000000000 --- a/tests/playwright/rte/ssh/keys/testp +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBPcEHCGN -DrMHhpQnPwc0XwAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIEs/ewkUXl0+uDr7 -hxSM2vURqdRNFHm7+x05azzW/YzuAAAAoEhNzctHXM6YBV0z4zzvdniQ5cLwsv8TfMZp2G -WUhZU05yugvKlRu1pml5q3XGSP5wYCF4vvi4BE563PMDKZWAqFFGtiTotEn+XuD/eP+P8H -xdf91tV5kE+1yvVwxUNMcijHY0uYopnG2NN3bdjOH/4YmW0WLyDu10EoMZKVnrP0qBbOrR -xKIy5lqa39SrAnUnGSoTEJsEWGLiIS2rBhkVc= ------END OPENSSH PRIVATE KEY----- From a6c99da8fb8051b7f7d953deab6b180201afdda2 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 14 May 2025 09:39:59 +0300 Subject: [PATCH 069/128] update config --- tests/e2e/.env | 85 ++++++++++++++++++++++++++ tests/e2e/local.web.docker-compose.yml | 68 ++++++++++----------- tests/e2e/rte.docker-compose.yml | 2 +- tests/playwright/helpers/conf.ts | 6 +- 4 files changed, 123 insertions(+), 38 deletions(-) diff --git a/tests/e2e/.env b/tests/e2e/.env index 9242dec68e..3a47cf8321 100644 --- a/tests/e2e/.env +++ b/tests/e2e/.env @@ -6,3 +6,88 @@ RI_NOTIFICATION_UPDATE_URL=https://s3.amazonaws.com/redisinsight.test/public/tes RI_NOTIFICATION_SYNC_INTERVAL=30000 RI_FEATURES_CONFIG_URL=http://static-server:5551/remote/features-config.json RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 +TEST_BIG_DB_DUMP=https://s3.amazonaws.com/redisinsight.test/public/rte/dump/big/dump.tar.gz +RI_ENCRYPTION_KEY=ba843af5c43bb6c90538534daf2dd0c2 +RI_SERVER_TLS_CERT='-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIUNPjaFDbh/Y0nCQzp5KYuvYc43zEwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMTAxNzIzNDVaFw0zNDAx +MDcxNzIzNDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCybIVgtjaaRgvJi/mZ91Qi42mXE0ueASXNn5qbMJ85 +dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kWE3lwCn5A1RPOBfDA8voSMPw4HSa9 +Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5fbJXM5uqf1fe1NE+HsUBKOdPBFNl4 +jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rFh3ggXZpDzq79ykLCK6MkaQcXYwls +qghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299v2vyRNm59u51c4g5dKxKPBPQw+06 +bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJyj8GP9Ej7XwQ9XQDiZc7/qzTamvE +yz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfIpHi/AJ//1yfPBqFLWY5B5f5fWQHv +e/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGDr9e25h3N3d6YRYKAMLiRxE2sFfni +79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQhA4gqNYIQ6KHvGDZCn6aNi8EgGYbp +PTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxMAUBesPIRj7kz5B6x3DfsBq/ZOzJW +Um35FPOdqGosqws31WatOTQIoXDV9lf86ew8e3W6O4O42szmcX2bVekNmGQTV9fX +OwIDAQABo1MwUTAdBgNVHQ4EFgQUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwHwYDVR0j +BBgwFoAUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAgEAk/YnVSsxvsNw43acfXHVNMffL3Uwy2v7UMdc0+nmuFtG +dj4RNVyW42jZXaeEzckEBr/HQLY0X4XA3SDRF6OXgdgyAFKSMChU0b7ldXTZicYO +cjOHztNBQ8NHXCSHJjzjJixgIKj88qAHzFNg89lA9RYjhiGhmR6iKe+FGpf8XyG9 +gF1mnqyWFGevsEgmNED/KgM1EZtix84hhuUvo1FoyjM4gkR6IQHkVwfDssRgZUaI +1TFURNLB3YSbjJ+vygBjYZbW29xLQ8MIZnT9uDmUoM1RKg33Fb3clsuF4U6hMOLo +TPFcBwrejY/3eMOZd1JqFN+JS4cRWhHVAk+Eurora76hBowMsYEsOBcrqDTZsFEU +x8qZbQLTCKy4HpvBAAH/P3gOeLEVYwCkTAA1w6/BLodu8dCJnKxylEuHoLSSm3xQ +pxRhb2dZHdd1gUikZ3rbFnUj9YDWz5eWn19FHkYlLXoxrQEkz20Mim7gJvbzLJbG +nPeVnoaFxr//sGJu+Ilq+heXeXqXHorOtfYbSlWJXUix67n+JvtVTHsdQ1PPMgGl +5hAD/oVNgdmWth/k6RDsKkrklkK5rEVkyeescpyhLRJOa/4l2xAQziBfQGijnTTb +NdFUmSSGFeptHPvjNLULF8kfAGEdmrGIttExvCwOci9F/k9OdmlTK2Z8UlGHwIA= +-----END CERTIFICATE-----' +RI_SERVER_TLS_KEY='-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCybIVgtjaaRgvJ +i/mZ91Qi42mXE0ueASXNn5qbMJ85dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kW +E3lwCn5A1RPOBfDA8voSMPw4HSa9Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5f +bJXM5uqf1fe1NE+HsUBKOdPBFNl4jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rF +h3ggXZpDzq79ykLCK6MkaQcXYwlsqghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299 +v2vyRNm59u51c4g5dKxKPBPQw+06bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJ +yj8GP9Ej7XwQ9XQDiZc7/qzTamvEyz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfI +pHi/AJ//1yfPBqFLWY5B5f5fWQHve/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGD +r9e25h3N3d6YRYKAMLiRxE2sFfni79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQh +A4gqNYIQ6KHvGDZCn6aNi8EgGYbpPTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxM +AUBesPIRj7kz5B6x3DfsBq/ZOzJWUm35FPOdqGosqws31WatOTQIoXDV9lf86ew8 +e3W6O4O42szmcX2bVekNmGQTV9fXOwIDAQABAoICAQCJAQFtoJzO22hjq4LOsfa+ +D2dd5SgUPIddm+dosO4ifwE+XXjCL1ITmkxbjVafK6URFH6tOqzdT6PrWqrN2JDX +kYXylecnEavp2glhwSilBanuiA5zxQfuZZxNZ3dUZhvmfqCK5gm066Cc31ErlEim +0PKzBmbnJ7jZBgxOX4cZpkmFLAjLBeF0sCYcLkN/bleAIW8J8xfWSclfUcVw/M7e +sE74e53FYSKVa7xRPe/Xq7xNantBkAOglvHj0AFJ1/1awiuHl+JDBtYidaEa17lj +rXOvZjY9ABTnr7f7fuajDN/uYl46blh2D0hXKNxAxvhlu4IufRCXCccqT80TLF+W +8Wlj6qysCESGTCn1xBnyriqv4ZtDgWS7gHEJqghdJr5x4q5BXpA2pWcyO5JSWrqg +kVE9rnV5j8c4hP86DNrtT4FdX9eAxMZvYmPOjF9TPOZGU7zDEpf00c7Gg9TZOlq7 +mUy7K0ybsgXQXTRDDv5prBRJ6A4mXBKZF2mQeUQPco90Wct3mAqTWPiUaCQ8+IPx +rR1BLGyTNwld8m9PxGyD8kQ3hkzMyUCh5ykqIJcRGUP4JS5xq29YJvABOxikJpRU +7QQ42k97mV7SVedPOSZFAoEhtgIbKh31OVNVulHNHac57AXwEvlLfPfmIrwSTFy6 +qElEkcFeqbxy8ZNiVJ7gMQKCAQEA3fT79JsCKtnjQSKEDdegWboW2m1jOk9f43jO +FiaaJia+i6ZuNzskOOK8F8J+C+nbWeKVRmsyCqsd+N+IB0X25PpcD1ZoXhXYBFp9 +QxANmY0LMv6d+iAv5wQ4ymQsLvjW0qdEgAKBKB7y/wnfITfbKmgm7zZzS4Qg/r6x +iPwY/1uxedJ/xC9ME7Vlo4p+pqq1pvS9ge8x51kuEB+GcmEGYBaCiVnazAZJSjcP +51EYdTQMt6nlPQu16YznlpazW8Nj5mrmxcMNbOJwphgyrOFTm3jWeaq6E3DR6Ion +FkI4hvC4f4sk5L6zVwkD5Vz0UhboZ6+VFwH7cXFN1Fh11pWkIwKCAQEAzco8VOZC +Qwwp81NEfXfuIfDRVBbTvv1W342HOSNdguWcqYDMnTE3herW4voOM4cN8tHiiybV +Ds5C3sVc+nG2mOdusP4PpQu8ZrC7eMYjFtSZjPDDBohkPuQFl1TXNvljiseM3o4m +DxQYnMxvR73zwYT6HVCY47+KqG4ll1xbqGtw2OxJFdTYgy5zqbsS6NVHqmp8Dytr +Jp5yX1sKY+uHnewJBIgzaVkQA2OFfQny5GQh1Gu3UZQGFYcxH15jeIFFQuf4FEyL +TiDGjnHOhz922Z7fGsom+vM+PrQqWhqlSAGURfnWE/9+7SQ/16nwgRPa+t7IR8m5 +qx6x/qr5svbGCQKCAQBCOyxD3U1URBhQA2CsUL+Ehsby/tkxOSY/1niIm5Q79iu9 +uDgDOx6f6uh9nofGPk46ECbDEJGqJU2YSftpUDNQOHToDryt9o6zHd1q+YtVWoUQ +/nFdheVFZjkcC7AzhAV2bns+R4EK29Fe0S9H1kcL7HBRyUm2KwM9gOGxIqjC6jWX +SHzfqc1lxCdEGbuZOC9kVnuGHj4h7raUERQpZVJlsdHZ8nobj3SnNK8GM2i88H8q +/wNsp+XsfyNMCEQVCcTxqMycDowfBaLfrTDR7ZrpNbGqNIu56Vx5q1/mgHQlsAcd +6ANmTpFtUz9aXdZ5+GP5LKesaecB/vFef9cJ5TVJAoIBAQCLWXiFHO6drCz0AdyN +AZsVDJcS0+pKmA4tjw6UGGiH7Keq9/aWMu+gFSexxxW6uqctWAaJp5/6SJ1rcEXH +qRy2aXDBFSjO4CWRe/dWjwTPvuLDV30YfV80Xv+SO/cH2NQY84tmYOdQqpEIDD8G +W5Al6L/c/eipv9hKuVtnJTlD0I03tri+ucRrABd+RZlGZLYEpdqgAwypt/1GqMVe +Z+0LePuaQAhgO9jmEowf4Y7CLTPjPZWC/jMofMU8spO01ARsi9unEzX5j6QkbNgn +KUh3kGcPIvhGUlRB7YoIPabSHY+j2sul+wqd1kAM75xWK3XLDvSb9/Nr9nsdMfWn +xAbRAoIBAQDFAiuvAuyUepnmJThP6hO9Xg90e6gD8c6zHs6ezIm8CRaOmb9FAM0l +nVz7oLsmgEuowrx1npTCzDr/RPBy94SpFbZMLzOL3cZVLd5AEDxsbt1cBUGy/Gda +dkV0J7YXeo0FVBKleM3GbpPjKgtmOMGfNmVKa3StkxvrRx4Nvro5DdhjM7CwBy7J +oNPYhCXXhvTzYvSFWQXmCJxDCKGeK7jKid2T9nlA/lCzR52m3LrBnAczTIGTPJDp +b1eyrSDuiaPs+gFn6Y94QmfNztstONlk0qRQpS+oTlmThNULJBfCJADIEXt9fVmR +flQdMlQ3lR1FtqYSypwbggaJ71j3gHU4 +-----END PRIVATE KEY-----' diff --git a/tests/e2e/local.web.docker-compose.yml b/tests/e2e/local.web.docker-compose.yml index 788a88304e..6abbe286ef 100644 --- a/tests/e2e/local.web.docker-compose.yml +++ b/tests/e2e/local.web.docker-compose.yml @@ -1,45 +1,45 @@ version: "3.4" services: - e2e: - build: - context: . - dockerfile: e2e.Dockerfile - tty: true - volumes: - - ./results:/usr/src/app/results - - ./report:/usr/src/app/report - - ./plugins:/usr/src/app/plugins - - rihomedir:/root/.redis-insight - - tmp:/tmp - - ./remote:/root/remote - # - ./rdi:/root/rdi - env_file: - - ./.env - entrypoint: [ - './upload-custom-plugins.sh', - ] - environment: - TEST_FILES: $TEST_FILES - E2E_CLOUD_DATABASE_HOST: $E2E_CLOUD_DATABASE_HOST - E2E_CLOUD_DATABASE_PORT: $E2E_CLOUD_DATABASE_PORT - E2E_CLOUD_DATABASE_PASSWORD: $E2E_CLOUD_DATABASE_PASSWORD - E2E_CLOUD_DATABASE_USERNAME: $E2E_CLOUD_DATABASE_USERNAME - E2E_CLOUD_DATABASE_NAME: $E2E_CLOUD_DATABASE_NAME - E2E_CLOUD_API_ACCESS_KEY: $E2E_CLOUD_API_ACCESS_KEY - E2E_CLOUD_API_SECRET_KEY: $E2E_CLOUD_API_SECRET_KEY - REMOTE_FOLDER_PATH: "/root/remote" - command: [ - './wait-for-it.sh', 'redis-enterprise:12000', '-s', '-t', '120', - '--', - 'npm', 'run', 'test:chrome:ci' - ] +# e2e: +# build: +# context: . +# dockerfile: e2e.Dockerfile +# tty: true +# volumes: +# - ./results:/usr/src/app/results +# - ./report:/usr/src/app/report +# - ./plugins:/usr/src/app/plugins +# - rihomedir:/root/.redis-insight +# - tmp:/tmp +# - ./remote:/root/remote +# # - ./rdi:/root/rdi +# env_file: +# - ./.env +# entrypoint: [ +# './upload-custom-plugins.sh', +# ] +# environment: +# TEST_FILES: $TEST_FILES +# E2E_CLOUD_DATABASE_HOST: $E2E_CLOUD_DATABASE_HOST +# E2E_CLOUD_DATABASE_PORT: $E2E_CLOUD_DATABASE_PORT +# E2E_CLOUD_DATABASE_PASSWORD: $E2E_CLOUD_DATABASE_PASSWORD +# E2E_CLOUD_DATABASE_USERNAME: $E2E_CLOUD_DATABASE_USERNAME +# E2E_CLOUD_DATABASE_NAME: $E2E_CLOUD_DATABASE_NAME +# E2E_CLOUD_API_ACCESS_KEY: $E2E_CLOUD_API_ACCESS_KEY +# E2E_CLOUD_API_SECRET_KEY: $E2E_CLOUD_API_SECRET_KEY +# REMOTE_FOLDER_PATH: "/root/remote" +# command: [ +# './wait-for-it.sh', 'redis-enterprise:12000', '-s', '-t', '120', +# '--', +# 'npm', 'run', 'test:chrome:ci' +# ] # Built image app: logging: driver: none - image: redisinsight:amd64 + image: redisinsight:arm64 env_file: - ./.env environment: diff --git a/tests/e2e/rte.docker-compose.yml b/tests/e2e/rte.docker-compose.yml index ba425cd5a1..f0f5cdbf94 100644 --- a/tests/e2e/rte.docker-compose.yml +++ b/tests/e2e/rte.docker-compose.yml @@ -3,7 +3,7 @@ version: "3.4" services: static-server: logging: &logging - driver: none + driver: json-file build: context: . dockerfile: static-server.Dockerfile diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts index d9fa343389..f55b70da59 100644 --- a/tests/playwright/helpers/conf.ts +++ b/tests/playwright/helpers/conf.ts @@ -156,12 +156,12 @@ export const ossStandaloneTlsConfig = { databasePassword: process.env.OSS_STANDALONE_TLS_PASSWORD, caCert: { name: `ca}-${uniqueId}`, - certificate: process.env.E2E_CA_CRT || fs.readFileSync(path.resolve(__dirname, '../local-docker-environment/rte/oss-standalone-tls/certs/redisCA.crt'), 'utf-8') + certificate: process.env.E2E_CA_CRT || fs.readFileSync(path.resolve(__dirname, '../../e2e/rte/oss-standalone-tls/certs/redisCA.crt'), 'utf-8') }, clientCert: { name: `client}-${uniqueId}`, - certificate: process.env.E2E_CLIENT_CRT || fs.readFileSync(path.resolve(__dirname, '../local-docker-environment/rte/oss-standalone-tls/certs/redis.crt'), 'utf-8'), - key: process.env.E2E_CLIENT_KEY || fs.readFileSync(path.resolve(__dirname, '../local-docker-environment/rte/oss-standalone-tls/certs/redis.key'), 'utf-8') + certificate: process.env.E2E_CLIENT_CRT || fs.readFileSync(path.resolve(__dirname, '../../e2e/rte/oss-standalone-tls/certs/redis.crt'), 'utf-8'), + key: process.env.E2E_CLIENT_KEY || fs.readFileSync(path.resolve(__dirname, '../../e2e/rte/oss-standalone-tls/certs/redis.key'), 'utf-8') } } From 88741dbb3cb4b91f15ff65a78e37b6fb32e6066b Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 4 Jun 2025 10:18:06 +0300 Subject: [PATCH 070/128] revert e2e/ folder changes --- tests/e2e/.desktop.env | 2 +- tests/e2e/helpers/database.ts | 11 +---------- .../e2e/tests/electron/smoke/browser/add-keys.e2e.ts | 2 +- .../tests/web/regression/browser/ttl-format.e2e.ts | 2 +- .../regression/insights/live-recommendations.e2e.ts | 4 ++-- tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts | 2 +- tests/e2e/web.runner.ts | 2 +- 7 files changed, 8 insertions(+), 17 deletions(-) diff --git a/tests/e2e/.desktop.env b/tests/e2e/.desktop.env index 52e686c61d..ba7c8f0112 100644 --- a/tests/e2e/.desktop.env +++ b/tests/e2e/.desktop.env @@ -1,4 +1,4 @@ -COMMON_URL=/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/resources/app.asar/dist/renderer/index.html +COMMON_URL=https://localhost:5530 API_URL=https://localhost:5530/api OSS_SENTINEL_PASSWORD=password RI_APP_FOLDER_NAME=.redis-insight-stage diff --git a/tests/e2e/helpers/database.ts b/tests/e2e/helpers/database.ts index a8ade61266..edc7dc1e97 100644 --- a/tests/e2e/helpers/database.ts +++ b/tests/e2e/helpers/database.ts @@ -10,7 +10,7 @@ import { BrowserPage, AutoDiscoverREDatabases } from '../pageObjects'; -import {OnboardingCardsDialog, UserAgreementDialog} from '../pageObjects/dialogs'; +import { UserAgreementDialog } from '../pageObjects/dialogs'; import { DatabaseAPIRequests } from './api/api-database'; import { RedisOverviewPage } from './constants'; import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page'; @@ -23,7 +23,6 @@ const browserPage = new BrowserPage(); const userAgreementDialog = new UserAgreementDialog(); const databaseAPIRequests = new DatabaseAPIRequests(); const rdiInstancesListPage = new RdiInstancesListPage(); -const onboardingCardsDialog = new OnboardingCardsDialog(); export class DatabaseHelper { /** @@ -214,14 +213,6 @@ export class DatabaseHelper { await myRedisDatabasePage.clickOnDBByName( databaseParameters.databaseName! ); - - - if (await Selector('span').withText('Skip tour').exists){ - await t.click(Selector('span').withText('Skip tour')) - } - - - } /** diff --git a/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts b/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts index 6dfa487e15..18b457f1fa 100644 --- a/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts +++ b/tests/e2e/tests/electron/smoke/browser/add-keys.e2e.ts @@ -35,7 +35,7 @@ test('Verify that user can add Hash Key', async t => { await browserPage.searchByKeyName(keyName); const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); -}).only; +}); test('Verify that user can add Set Key', async t => { keyName = Common.generateWord(10); // Add Set key diff --git a/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts b/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts index cfdb202637..8e91c20f0e 100644 --- a/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts +++ b/tests/e2e/tests/web/regression/browser/ttl-format.e2e.ts @@ -33,7 +33,7 @@ fixture `TTL values in Keys Table` // Clear and delete database await deleteKeysViaCli(keysData); await databaseAPIRequests.deleteStandaloneDatabaseApi(ossStandaloneConfig); - }).skip; + }); test('Verify that user can see TTL in the list of keys rounded down to the nearest unit', async t => { // Create new keys with TTL await t.click(browserPage.Cli.cliExpandButton); diff --git a/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts b/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts index 04dcd5f831..439d57d790 100644 --- a/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts +++ b/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts @@ -53,7 +53,7 @@ fixture `Live Recommendations` await databaseAPIRequests.addNewStandaloneDatabaseApi(ossStandaloneV6Config); await myRedisDatabasePage.reloadPage(); await myRedisDatabasePage.clickOnDBByName(ossStandaloneV6Config.databaseName); - }).only + }) .afterEach(async() => { await refreshFeaturesTestData(); // Delete database @@ -156,7 +156,7 @@ test await recommendationsActions.voteForRecommendation(redisVersionRecom, usefulVoteOption); // Verify that user can rate recommendations with one of 2 existing types at the same time await recommendationsActions.verifyVoteIsSelected(redisVersionRecom, usefulVoteOption); - }).only; + }); test('Verify that user can hide recommendations and checkbox value is saved', async t => { const commandToGetRecommendation = 'FT.INFO'; await browserPage.Cli.sendCommandInCli(commandToGetRecommendation); diff --git a/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts b/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts index 4254158c8f..27fa2e24e2 100644 --- a/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts +++ b/tests/e2e/tests/web/smoke/browser/add-keys.e2e.ts @@ -35,7 +35,7 @@ test('Verify that user can add Hash Key', async t => { await browserPage.searchByKeyName(keyName); const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName); await t.expect(isKeyIsDisplayedInTheList).ok('The Hash key is not added'); -}).only +}); test('Verify that user can add Set Key', async t => { keyName = Common.generateWord(10); // Add Set key diff --git a/tests/e2e/web.runner.ts b/tests/e2e/web.runner.ts index 9715ac6903..69802c3e1d 100644 --- a/tests/e2e/web.runner.ts +++ b/tests/e2e/web.runner.ts @@ -11,7 +11,7 @@ import testcafe from 'testcafe'; experimentalDecorators: true } }) .src((process.env.TEST_FILES || 'tests/web/**/*.e2e.ts').split('\n')) - .browsers(['chrome --cache --allow-insecure-localhost --disable-search-engine-choice-screen --ignore-certificate-errors']) + .browsers(['chromium --disable-search-engine-choice-screen --ignore-certificate-errors --disable-dev-shm-usage --no-sandbox']) .screenshots({ path: 'report/screenshots/', takeOnFails: true, From e8f5686174019d919589794cba15586912bdaae7 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 4 Jun 2025 10:31:57 +0300 Subject: [PATCH 071/128] refactor fixture --- tests/playwright/fixtures/simple-slectron.ts | 164 ++++++++++--------- 1 file changed, 84 insertions(+), 80 deletions(-) diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index e6a47a2937..dec8bf1deb 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -1,136 +1,140 @@ -import { test as base, ElectronApplication, Page } from '@playwright/test'; -import { _electron as electron } from 'playwright'; -import { ossStandaloneConfig } from '../helpers/conf'; -import log from "node-color-log"; -import * as dotenv from "dotenv"; -import path from "node:path"; - -dotenv.config({ path: path.resolve(__dirname, "..",'.desktop.env') }) +/* eslint-disable no-param-reassign */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-await-in-loop */ +import { test as base, ElectronApplication, Page } from '@playwright/test' +import { _electron as electron } from 'playwright' +import log from 'node-color-log' +import * as dotenv from 'dotenv' +import path from 'node:path' +import { ossStandaloneConfig } from '../helpers/conf' + +dotenv.config({ path: path.resolve(__dirname, '..', '.desktop.env') }) // Define shared state for worker scope type WorkerSharedState = { - apiUrl: string; - dbConfig: typeof ossStandaloneConfig; - baseUrl: string; - electronApp: ElectronApplication; -}; + apiUrl: string + dbConfig: typeof ossStandaloneConfig + baseUrl: string + electronApp: ElectronApplication +} type ElectronFixture = { - electronApp: ElectronApplication; - electronPage: Page; -}; + electronApp: ElectronApplication + electronPage: Page +} -async function launchElectronApp(baseUrl: string): Promise { +async function launchElectronApp( + baseUrl: string, +): Promise { const electronApp = await electron.launch({ executablePath: baseUrl, args: ['index.html'], timeout: 60000, - }); + }) // Capture Electron logs electronApp.on('console', (msg) => { - log.info(`Electron Log: ${msg.type()} - ${msg.text()}`); - }); + log.info(`Electron Log: ${msg.type()} - ${msg.text()}`) + }) - return electronApp; + return electronApp } async function waitForWindowWithTitle( electronApp: ElectronApplication, maxWaitTime = 5000, - interval = 200 + interval = 200, ): Promise { - const startTime = Date.now(); + const startTime = Date.now() while (Date.now() - startTime < maxWaitTime) { - const windows = await electronApp.windows(); - let title: string; + const windows = await electronApp.windows() for (const window of windows) { - let title: string; - try{ - title = await window.title(); - }catch(e){ - log.info("❌ Window title not found") - } - - if (title) { - log.info(`✅ Found window with title: "${title}"`); - return window; + try { + const title = await window.title() + + if (title) { + log.info(`✅ Found window with title: "${title}"`) + return window + } + } catch (e) { + log.info('❌ Window title not found') } } - log.info(`🔍 Checking for window with title "${title}"...`); - await new Promise((resolve) => setTimeout(resolve, interval)); + await new Promise((resolve) => setTimeout(resolve, interval)) } - log.error(`❌ Window not found within ${maxWaitTime / 1000}s!`); - throw Error("❌ Window not found ") - return; + log.error(`❌ Window not found within ${maxWaitTime / 1000}s!`) + throw Error('❌ Window not found ') } - -async function waitForWindows(electronApp: ElectronApplication, maxWaitTime = 60000, interval = 2000) { - let windows = []; - let elapsedTime = 0; +async function waitForWindows( + electronApp: ElectronApplication, + maxWaitTime = 60000, + interval = 2000, +) { + let windows: Page[] = [] + let elapsedTime = 0 while (windows.length === 0 && elapsedTime < maxWaitTime) { - await new Promise((resolve) => setTimeout(resolve, interval)); - windows = await electronApp.windows(); - elapsedTime += interval; - log.info(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); + await new Promise((resolve) => setTimeout(resolve, interval)) + windows = await electronApp.windows() + elapsedTime += interval + log.info(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`) } - return windows; + return windows } - -export const test = base.extend({ +export const test = base.extend< + ElectronFixture, + { workerState: WorkerSharedState } +>({ workerState: [ - async ({}, use, testInfo) => { - log.info(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`); + async (_, use, testInfo) => { + log.info( + `🚀 Setting up worker state for worker ${testInfo.workerIndex}`, + ) const workerState: WorkerSharedState = { apiUrl: testInfo.project.use.apiUrl, dbConfig: ossStandaloneConfig, - baseUrl: testInfo.project.use.baseURL, + baseUrl: testInfo.project.use.baseURL || '', electronApp: null as any, - }; - + } - await use(workerState); + await use(workerState) }, { scope: 'worker' }, ], electronApp: async ({ workerState }, use) => { - log.info('🚀 Starting RedisInsight...'); - - // update control nmb - // await updateControlNumber(48.2, workerState.apiUrl); + log.info('🚀 Starting RedisInsight...') - let electronApp = await launchElectronApp(workerState.baseUrl); - workerState.electronApp = electronApp; - log.info('⏳ Waiting for window...'); - let windows = await waitForWindows(electronApp); + const electronApp = await launchElectronApp(workerState.baseUrl) + workerState.electronApp = electronApp + log.info('⏳ Waiting for window...') + const windows = await waitForWindows(electronApp) if (windows.length === 0) { - log.error('❌ No windows detected after 60s! Exiting.'); - await electronApp.close(); - return; + log.error('❌ No windows detected after 60s! Exiting.') + await electronApp.close() + return } - log.info(`✅ Found ${windows.length} window(s)!`); - await use(electronApp); + log.info(`✅ Found ${windows.length} window(s)!`) + await use(electronApp) }, - electronPage: async ({ electronApp,workerState }, use) => { - let window = await waitForWindowWithTitle(electronApp); + electronPage: async ({ electronApp }, use) => { + const window = await waitForWindowWithTitle(electronApp) if (!window) { - log.error('❌ No matching window detected! Stopping test.'); - await electronApp.close(); - return; + log.error('❌ No matching window detected! Stopping test.') + await electronApp.close() + return } - await window.waitForLoadState('domcontentloaded'); - log.info(`🖥️ Window Title: ${await window.title()}`); - await use(window); + await window.waitForLoadState('domcontentloaded') + log.info(`🖥️ Window Title: ${await window.title()}`) + await use(window) }, -}); +}) -export { expect } from '@playwright/test'; +export { expect } from '@playwright/test' From 018be1e61a8a73658883e41f06e9ff704dd31745 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 4 Jun 2025 11:03:00 +0300 Subject: [PATCH 072/128] resolve docker environments --- .github/workflows/tests-e2e-docker.yml | 2 +- tests/e2e/docker.web.docker-compose.yml | 2 + tests/e2e/local.web.docker-compose.yml | 68 ++-- tests/playwright/.desktop.env | 4 +- .../playwright/local-docker-environment/.env | 93 ------ .../create_local_environment.sh | 23 -- .../destroy_local_environment.sh | 5 - .../local-docker-environment/e2e.Dockerfile | 18 -- .../local.web.docker-compose.yml | 59 ---- .../rte.docker-compose.yml | 299 ------------------ 10 files changed, 40 insertions(+), 533 deletions(-) delete mode 100644 tests/playwright/local-docker-environment/.env delete mode 100755 tests/playwright/local-docker-environment/create_local_environment.sh delete mode 100755 tests/playwright/local-docker-environment/destroy_local_environment.sh delete mode 100644 tests/playwright/local-docker-environment/e2e.Dockerfile delete mode 100644 tests/playwright/local-docker-environment/local.web.docker-compose.yml delete mode 100644 tests/playwright/local-docker-environment/rte.docker-compose.yml diff --git a/.github/workflows/tests-e2e-docker.yml b/.github/workflows/tests-e2e-docker.yml index ece239ee49..b0b0e72297 100644 --- a/.github/workflows/tests-e2e-docker.yml +++ b/.github/workflows/tests-e2e-docker.yml @@ -83,7 +83,7 @@ jobs: TEST_BIG_DB_DUMP=$TEST_BIG_DB_DUMP \ RI_SERVER_TLS_CERT="$RI_SERVER_TLS_CERT" \ RI_SERVER_TLS_KEY="$RI_SERVER_TLS_KEY" \ - docker compose \ + docker compose --profile e2e \ -f tests/e2e/rte.docker-compose.yml \ -f tests/e2e/docker.web.docker-compose.yml \ up --abort-on-container-exit --force-recreate diff --git a/tests/e2e/docker.web.docker-compose.yml b/tests/e2e/docker.web.docker-compose.yml index be7bdc84b3..f1c417376a 100644 --- a/tests/e2e/docker.web.docker-compose.yml +++ b/tests/e2e/docker.web.docker-compose.yml @@ -2,6 +2,8 @@ version: "3.4" services: e2e: + profiles: + - e2e build: context: . dockerfile: e2e.Dockerfile diff --git a/tests/e2e/local.web.docker-compose.yml b/tests/e2e/local.web.docker-compose.yml index 6abbe286ef..985ef0d01f 100644 --- a/tests/e2e/local.web.docker-compose.yml +++ b/tests/e2e/local.web.docker-compose.yml @@ -1,39 +1,41 @@ version: "3.4" services: -# e2e: -# build: -# context: . -# dockerfile: e2e.Dockerfile -# tty: true -# volumes: -# - ./results:/usr/src/app/results -# - ./report:/usr/src/app/report -# - ./plugins:/usr/src/app/plugins -# - rihomedir:/root/.redis-insight -# - tmp:/tmp -# - ./remote:/root/remote -# # - ./rdi:/root/rdi -# env_file: -# - ./.env -# entrypoint: [ -# './upload-custom-plugins.sh', -# ] -# environment: -# TEST_FILES: $TEST_FILES -# E2E_CLOUD_DATABASE_HOST: $E2E_CLOUD_DATABASE_HOST -# E2E_CLOUD_DATABASE_PORT: $E2E_CLOUD_DATABASE_PORT -# E2E_CLOUD_DATABASE_PASSWORD: $E2E_CLOUD_DATABASE_PASSWORD -# E2E_CLOUD_DATABASE_USERNAME: $E2E_CLOUD_DATABASE_USERNAME -# E2E_CLOUD_DATABASE_NAME: $E2E_CLOUD_DATABASE_NAME -# E2E_CLOUD_API_ACCESS_KEY: $E2E_CLOUD_API_ACCESS_KEY -# E2E_CLOUD_API_SECRET_KEY: $E2E_CLOUD_API_SECRET_KEY -# REMOTE_FOLDER_PATH: "/root/remote" -# command: [ -# './wait-for-it.sh', 'redis-enterprise:12000', '-s', '-t', '120', -# '--', -# 'npm', 'run', 'test:chrome:ci' -# ] + e2e: + profiles: + - e2e + build: + context: . + dockerfile: e2e.Dockerfile + tty: true + volumes: + - ./results:/usr/src/app/results + - ./report:/usr/src/app/report + - ./plugins:/usr/src/app/plugins + - rihomedir:/root/.redis-insight + - tmp:/tmp + - ./remote:/root/remote + # - ./rdi:/root/rdi + env_file: + - ./.env + entrypoint: [ + './upload-custom-plugins.sh', + ] + environment: + TEST_FILES: $TEST_FILES + E2E_CLOUD_DATABASE_HOST: $E2E_CLOUD_DATABASE_HOST + E2E_CLOUD_DATABASE_PORT: $E2E_CLOUD_DATABASE_PORT + E2E_CLOUD_DATABASE_PASSWORD: $E2E_CLOUD_DATABASE_PASSWORD + E2E_CLOUD_DATABASE_USERNAME: $E2E_CLOUD_DATABASE_USERNAME + E2E_CLOUD_DATABASE_NAME: $E2E_CLOUD_DATABASE_NAME + E2E_CLOUD_API_ACCESS_KEY: $E2E_CLOUD_API_ACCESS_KEY + E2E_CLOUD_API_SECRET_KEY: $E2E_CLOUD_API_SECRET_KEY + REMOTE_FOLDER_PATH: "/root/remote" + command: [ + './wait-for-it.sh', 'redis-enterprise:12000', '-s', '-t', '120', + '--', + 'npm', 'run', 'test:chrome:ci' + ] # Built image app: diff --git a/tests/playwright/.desktop.env b/tests/playwright/.desktop.env index 7008dda6ba..ba7c8f0112 100644 --- a/tests/playwright/.desktop.env +++ b/tests/playwright/.desktop.env @@ -1,5 +1,5 @@ -#COMMON_URL=/home/tsvetan-tsvetkov/code/RedisInsight/tests/e2e/electronBuild/resources/app.asar/dist/renderer/index.html -#API_URL=https://localhost:5530/api +COMMON_URL=https://localhost:5530 +API_URL=https://localhost:5530/api OSS_SENTINEL_PASSWORD=password RI_APP_FOLDER_NAME=.redis-insight-stage diff --git a/tests/playwright/local-docker-environment/.env b/tests/playwright/local-docker-environment/.env deleted file mode 100644 index 3a47cf8321..0000000000 --- a/tests/playwright/local-docker-environment/.env +++ /dev/null @@ -1,93 +0,0 @@ -COMMON_URL=https://app:5540 -API_URL=https://app:5540/api -BUILD_TYPE=DOCKER_ON_PREMISE -OSS_SENTINEL_PASSWORD=password -RI_NOTIFICATION_UPDATE_URL=https://s3.amazonaws.com/redisinsight.test/public/tests/e2e/notifications.json -RI_NOTIFICATION_SYNC_INTERVAL=30000 -RI_FEATURES_CONFIG_URL=http://static-server:5551/remote/features-config.json -RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 -TEST_BIG_DB_DUMP=https://s3.amazonaws.com/redisinsight.test/public/rte/dump/big/dump.tar.gz -RI_ENCRYPTION_KEY=ba843af5c43bb6c90538534daf2dd0c2 -RI_SERVER_TLS_CERT='-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIUNPjaFDbh/Y0nCQzp5KYuvYc43zEwDQYJKoZIhvcNAQEL -BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMTAxNzIzNDVaFw0zNDAx -MDcxNzIzNDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCybIVgtjaaRgvJi/mZ91Qi42mXE0ueASXNn5qbMJ85 -dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kWE3lwCn5A1RPOBfDA8voSMPw4HSa9 -Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5fbJXM5uqf1fe1NE+HsUBKOdPBFNl4 -jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rFh3ggXZpDzq79ykLCK6MkaQcXYwls -qghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299v2vyRNm59u51c4g5dKxKPBPQw+06 -bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJyj8GP9Ej7XwQ9XQDiZc7/qzTamvE -yz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfIpHi/AJ//1yfPBqFLWY5B5f5fWQHv -e/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGDr9e25h3N3d6YRYKAMLiRxE2sFfni -79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQhA4gqNYIQ6KHvGDZCn6aNi8EgGYbp -PTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxMAUBesPIRj7kz5B6x3DfsBq/ZOzJW -Um35FPOdqGosqws31WatOTQIoXDV9lf86ew8e3W6O4O42szmcX2bVekNmGQTV9fX -OwIDAQABo1MwUTAdBgNVHQ4EFgQUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwHwYDVR0j -BBgwFoAUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAgEAk/YnVSsxvsNw43acfXHVNMffL3Uwy2v7UMdc0+nmuFtG -dj4RNVyW42jZXaeEzckEBr/HQLY0X4XA3SDRF6OXgdgyAFKSMChU0b7ldXTZicYO -cjOHztNBQ8NHXCSHJjzjJixgIKj88qAHzFNg89lA9RYjhiGhmR6iKe+FGpf8XyG9 -gF1mnqyWFGevsEgmNED/KgM1EZtix84hhuUvo1FoyjM4gkR6IQHkVwfDssRgZUaI -1TFURNLB3YSbjJ+vygBjYZbW29xLQ8MIZnT9uDmUoM1RKg33Fb3clsuF4U6hMOLo -TPFcBwrejY/3eMOZd1JqFN+JS4cRWhHVAk+Eurora76hBowMsYEsOBcrqDTZsFEU -x8qZbQLTCKy4HpvBAAH/P3gOeLEVYwCkTAA1w6/BLodu8dCJnKxylEuHoLSSm3xQ -pxRhb2dZHdd1gUikZ3rbFnUj9YDWz5eWn19FHkYlLXoxrQEkz20Mim7gJvbzLJbG -nPeVnoaFxr//sGJu+Ilq+heXeXqXHorOtfYbSlWJXUix67n+JvtVTHsdQ1PPMgGl -5hAD/oVNgdmWth/k6RDsKkrklkK5rEVkyeescpyhLRJOa/4l2xAQziBfQGijnTTb -NdFUmSSGFeptHPvjNLULF8kfAGEdmrGIttExvCwOci9F/k9OdmlTK2Z8UlGHwIA= ------END CERTIFICATE-----' -RI_SERVER_TLS_KEY='-----BEGIN PRIVATE KEY----- -MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCybIVgtjaaRgvJ -i/mZ91Qi42mXE0ueASXNn5qbMJ85dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kW -E3lwCn5A1RPOBfDA8voSMPw4HSa9Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5f -bJXM5uqf1fe1NE+HsUBKOdPBFNl4jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rF -h3ggXZpDzq79ykLCK6MkaQcXYwlsqghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299 -v2vyRNm59u51c4g5dKxKPBPQw+06bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJ -yj8GP9Ej7XwQ9XQDiZc7/qzTamvEyz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfI -pHi/AJ//1yfPBqFLWY5B5f5fWQHve/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGD -r9e25h3N3d6YRYKAMLiRxE2sFfni79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQh -A4gqNYIQ6KHvGDZCn6aNi8EgGYbpPTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxM -AUBesPIRj7kz5B6x3DfsBq/ZOzJWUm35FPOdqGosqws31WatOTQIoXDV9lf86ew8 -e3W6O4O42szmcX2bVekNmGQTV9fXOwIDAQABAoICAQCJAQFtoJzO22hjq4LOsfa+ -D2dd5SgUPIddm+dosO4ifwE+XXjCL1ITmkxbjVafK6URFH6tOqzdT6PrWqrN2JDX -kYXylecnEavp2glhwSilBanuiA5zxQfuZZxNZ3dUZhvmfqCK5gm066Cc31ErlEim -0PKzBmbnJ7jZBgxOX4cZpkmFLAjLBeF0sCYcLkN/bleAIW8J8xfWSclfUcVw/M7e -sE74e53FYSKVa7xRPe/Xq7xNantBkAOglvHj0AFJ1/1awiuHl+JDBtYidaEa17lj -rXOvZjY9ABTnr7f7fuajDN/uYl46blh2D0hXKNxAxvhlu4IufRCXCccqT80TLF+W -8Wlj6qysCESGTCn1xBnyriqv4ZtDgWS7gHEJqghdJr5x4q5BXpA2pWcyO5JSWrqg -kVE9rnV5j8c4hP86DNrtT4FdX9eAxMZvYmPOjF9TPOZGU7zDEpf00c7Gg9TZOlq7 -mUy7K0ybsgXQXTRDDv5prBRJ6A4mXBKZF2mQeUQPco90Wct3mAqTWPiUaCQ8+IPx -rR1BLGyTNwld8m9PxGyD8kQ3hkzMyUCh5ykqIJcRGUP4JS5xq29YJvABOxikJpRU -7QQ42k97mV7SVedPOSZFAoEhtgIbKh31OVNVulHNHac57AXwEvlLfPfmIrwSTFy6 -qElEkcFeqbxy8ZNiVJ7gMQKCAQEA3fT79JsCKtnjQSKEDdegWboW2m1jOk9f43jO -FiaaJia+i6ZuNzskOOK8F8J+C+nbWeKVRmsyCqsd+N+IB0X25PpcD1ZoXhXYBFp9 -QxANmY0LMv6d+iAv5wQ4ymQsLvjW0qdEgAKBKB7y/wnfITfbKmgm7zZzS4Qg/r6x -iPwY/1uxedJ/xC9ME7Vlo4p+pqq1pvS9ge8x51kuEB+GcmEGYBaCiVnazAZJSjcP -51EYdTQMt6nlPQu16YznlpazW8Nj5mrmxcMNbOJwphgyrOFTm3jWeaq6E3DR6Ion -FkI4hvC4f4sk5L6zVwkD5Vz0UhboZ6+VFwH7cXFN1Fh11pWkIwKCAQEAzco8VOZC -Qwwp81NEfXfuIfDRVBbTvv1W342HOSNdguWcqYDMnTE3herW4voOM4cN8tHiiybV -Ds5C3sVc+nG2mOdusP4PpQu8ZrC7eMYjFtSZjPDDBohkPuQFl1TXNvljiseM3o4m -DxQYnMxvR73zwYT6HVCY47+KqG4ll1xbqGtw2OxJFdTYgy5zqbsS6NVHqmp8Dytr -Jp5yX1sKY+uHnewJBIgzaVkQA2OFfQny5GQh1Gu3UZQGFYcxH15jeIFFQuf4FEyL -TiDGjnHOhz922Z7fGsom+vM+PrQqWhqlSAGURfnWE/9+7SQ/16nwgRPa+t7IR8m5 -qx6x/qr5svbGCQKCAQBCOyxD3U1URBhQA2CsUL+Ehsby/tkxOSY/1niIm5Q79iu9 -uDgDOx6f6uh9nofGPk46ECbDEJGqJU2YSftpUDNQOHToDryt9o6zHd1q+YtVWoUQ -/nFdheVFZjkcC7AzhAV2bns+R4EK29Fe0S9H1kcL7HBRyUm2KwM9gOGxIqjC6jWX -SHzfqc1lxCdEGbuZOC9kVnuGHj4h7raUERQpZVJlsdHZ8nobj3SnNK8GM2i88H8q -/wNsp+XsfyNMCEQVCcTxqMycDowfBaLfrTDR7ZrpNbGqNIu56Vx5q1/mgHQlsAcd -6ANmTpFtUz9aXdZ5+GP5LKesaecB/vFef9cJ5TVJAoIBAQCLWXiFHO6drCz0AdyN -AZsVDJcS0+pKmA4tjw6UGGiH7Keq9/aWMu+gFSexxxW6uqctWAaJp5/6SJ1rcEXH -qRy2aXDBFSjO4CWRe/dWjwTPvuLDV30YfV80Xv+SO/cH2NQY84tmYOdQqpEIDD8G -W5Al6L/c/eipv9hKuVtnJTlD0I03tri+ucRrABd+RZlGZLYEpdqgAwypt/1GqMVe -Z+0LePuaQAhgO9jmEowf4Y7CLTPjPZWC/jMofMU8spO01ARsi9unEzX5j6QkbNgn -KUh3kGcPIvhGUlRB7YoIPabSHY+j2sul+wqd1kAM75xWK3XLDvSb9/Nr9nsdMfWn -xAbRAoIBAQDFAiuvAuyUepnmJThP6hO9Xg90e6gD8c6zHs6ezIm8CRaOmb9FAM0l -nVz7oLsmgEuowrx1npTCzDr/RPBy94SpFbZMLzOL3cZVLd5AEDxsbt1cBUGy/Gda -dkV0J7YXeo0FVBKleM3GbpPjKgtmOMGfNmVKa3StkxvrRx4Nvro5DdhjM7CwBy7J -oNPYhCXXhvTzYvSFWQXmCJxDCKGeK7jKid2T9nlA/lCzR52m3LrBnAczTIGTPJDp -b1eyrSDuiaPs+gFn6Y94QmfNztstONlk0qRQpS+oTlmThNULJBfCJADIEXt9fVmR -flQdMlQ3lR1FtqYSypwbggaJ71j3gHU4 ------END PRIVATE KEY-----' diff --git a/tests/playwright/local-docker-environment/create_local_environment.sh b/tests/playwright/local-docker-environment/create_local_environment.sh deleted file mode 100755 index d127bdb9ab..0000000000 --- a/tests/playwright/local-docker-environment/create_local_environment.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Create directory and set write permissions -mkdir -p "rihomedir" -chmod +w rihomedir/ - -# Display options to the user -echo "Choose an option:" -echo "1) Start Redis databases without Redis Insight UI" -echo "2) Start Redis databases and Redis Insight UI - docker container must be loaded" -read -p "Enter your choice [1 or 2]: " choice - -# Execute based on user choice -if [ "$choice" == "1" ]; then - echo "Starting environment without Redis Insight UI..." - docker compose -p test-docker -f rte.docker-compose.yml up -d -elif [ "$choice" == "2" ]; then - echo "Starting environment with Redis Insight UI..." - docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml up -d -else - echo "Invalid option. Please enter 1 or 2." - exit 1 -fi diff --git a/tests/playwright/local-docker-environment/destroy_local_environment.sh b/tests/playwright/local-docker-environment/destroy_local_environment.sh deleted file mode 100755 index 203a22e443..0000000000 --- a/tests/playwright/local-docker-environment/destroy_local_environment.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -docker compose -p test-docker -f rte.docker-compose.yml -f local.web.docker-compose.yml down && -sleep 3 -rm -rf plugins remote report results rte test-data rihomedir - diff --git a/tests/playwright/local-docker-environment/e2e.Dockerfile b/tests/playwright/local-docker-environment/e2e.Dockerfile deleted file mode 100644 index 306c5a77f9..0000000000 --- a/tests/playwright/local-docker-environment/e2e.Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM testcafe/testcafe - -USER root - -WORKDIR /usr/src/app - -RUN apk add --no-cache bash curl - -COPY package.json yarn.lock ./ - -RUN npx yarn - -COPY . . - -RUN chmod +x wait-for-it.sh -RUN chmod +x upload-custom-plugins.sh - -ENTRYPOINT ["npx", "yarn", "test:chrome:ci"] diff --git a/tests/playwright/local-docker-environment/local.web.docker-compose.yml b/tests/playwright/local-docker-environment/local.web.docker-compose.yml deleted file mode 100644 index 788a88304e..0000000000 --- a/tests/playwright/local-docker-environment/local.web.docker-compose.yml +++ /dev/null @@ -1,59 +0,0 @@ -version: "3.4" - -services: - e2e: - build: - context: . - dockerfile: e2e.Dockerfile - tty: true - volumes: - - ./results:/usr/src/app/results - - ./report:/usr/src/app/report - - ./plugins:/usr/src/app/plugins - - rihomedir:/root/.redis-insight - - tmp:/tmp - - ./remote:/root/remote - # - ./rdi:/root/rdi - env_file: - - ./.env - entrypoint: [ - './upload-custom-plugins.sh', - ] - environment: - TEST_FILES: $TEST_FILES - E2E_CLOUD_DATABASE_HOST: $E2E_CLOUD_DATABASE_HOST - E2E_CLOUD_DATABASE_PORT: $E2E_CLOUD_DATABASE_PORT - E2E_CLOUD_DATABASE_PASSWORD: $E2E_CLOUD_DATABASE_PASSWORD - E2E_CLOUD_DATABASE_USERNAME: $E2E_CLOUD_DATABASE_USERNAME - E2E_CLOUD_DATABASE_NAME: $E2E_CLOUD_DATABASE_NAME - E2E_CLOUD_API_ACCESS_KEY: $E2E_CLOUD_API_ACCESS_KEY - E2E_CLOUD_API_SECRET_KEY: $E2E_CLOUD_API_SECRET_KEY - REMOTE_FOLDER_PATH: "/root/remote" - command: [ - './wait-for-it.sh', 'redis-enterprise:12000', '-s', '-t', '120', - '--', - 'npm', 'run', 'test:chrome:ci' - ] - - # Built image - app: - logging: - driver: none - image: redisinsight:amd64 - env_file: - - ./.env - environment: - RI_ENCRYPTION_KEY: $RI_ENCRYPTION_KEY - RI_SERVER_TLS_CERT: $RI_SERVER_TLS_CERT - RI_SERVER_TLS_KEY: $RI_SERVER_TLS_KEY - BUILD_TYPE: DOCKER_ON_PREMISE - volumes: - - ./rihomedir:/data - - tmp:/tmp - - ./test-data:/test-data - ports: - - 5540:5540 - -volumes: - tmp: - rihomedir: diff --git a/tests/playwright/local-docker-environment/rte.docker-compose.yml b/tests/playwright/local-docker-environment/rte.docker-compose.yml deleted file mode 100644 index ba425cd5a1..0000000000 --- a/tests/playwright/local-docker-environment/rte.docker-compose.yml +++ /dev/null @@ -1,299 +0,0 @@ -version: "3.4" - -services: - static-server: - logging: &logging - driver: none - build: - context: . - dockerfile: static-server.Dockerfile - volumes: - - ./remote:/app/remote - ports: - - 5551:5551 - # RDI mocked - # rdi: - # logging: *logging - # build: - # context: rte/rdi - # dockerfile: Dockerfile - # volumes: - # - ./rdi:/data - # ports: - # - 4000:4000 - # ssh - ssh: - logging: *logging - image: lscr.io/linuxserver/openssh-server:9.7_p1-r4-ls172 - environment: - - PASSWORD_ACCESS=true - - USER_PASSWORD=pass - - USER_NAME=u - - DOCKER_MODS=linuxserver/mods:openssh-server-ssh-tunnel - - PUBLIC_KEY_DIR=/keys/pub - volumes: - - ./rte/ssh/keys:/keys - ports: - - 2222:2222 - networks: - default: - ipv4_address: 172.31.100.245 - ssh: - ipv4_address: 172.33.100.245 - - # oss standalone - oss-standalone: - logging: *logging - image: redislabs/redismod - command: [ - "--loadmodule", "/usr/lib/redis/modules/redisearch.so", - "--loadmodule", "/usr/lib/redis/modules/redisgraph.so", - "--loadmodule", "/usr/lib/redis/modules/redistimeseries.so", - "--loadmodule", "/usr/lib/redis/modules/rejson.so", - "--loadmodule", "/usr/lib/redis/modules/redisbloom.so" - ] - ports: - - 8100:6379 - - oss-standalone-empty: - logging: *logging - image: redislabs/redismod - command: [ - "--loadmodule", "/usr/lib/redis/modules/redisearch.so", - "--loadmodule", "/usr/lib/redis/modules/redisgraph.so", - "--loadmodule", "/usr/lib/redis/modules/redistimeseries.so", - "--loadmodule", "/usr/lib/redis/modules/rejson.so", - "--loadmodule", "/usr/lib/redis/modules/redisbloom.so" - ] - ports: - - 8105:6379 - - # oss standalone v5 - oss-standalone-v5: - logging: *logging - image: redis:5 - ports: - - 8101:6379 - networks: - default: - ipv4_address: 172.31.100.111 - ssh: - ipv4_address: 172.33.100.111 - - # oss standalone v7 - oss-standalone-v7: - logging: *logging - image: redis:7.4-rc2 - ports: - - 8108:6379 - networks: - default: - ipv4_address: 172.31.100.112 - ssh: - ipv4_address: 172.33.100.112 - - # oss standalone v8 - oss-standalone-v8: - logging: *logging - image: redis:8.0-M02 - ports: - - 8109:6379 - networks: - default: - ipv4_address: 172.31.100.113 - ssh: - ipv4_address: 172.33.100.113 - - # oss standalone redisearch - oss-standalone-redisearch: - logging: *logging - image: redislabs/redismod - ports: - - 8102:6379 - - oss-standalone-redisgears-2-0: - logging: *logging - image: redislabs/redisgears:edge - ports: - - 8106:6379 - - oss-standalone-big: - logging: *logging - build: - context: ./rte/oss-standalone-big - dockerfile: Dockerfile - args: - TEST_DB_DUMP: $TEST_BIG_DB_DUMP - ports: - - 8103:6379 - - # oss standalone tls - oss-standalone-tls: - logging: *logging - build: - context: ./rte/oss-standalone-tls - dockerfile: Dockerfile - ports: - - 8104:6379 - - # oss sentinel - oss-sentinel: - logging: *logging - build: ./rte/oss-sentinel - depends_on: - - oss-sentinel-primary-1 - - oss-sentinel-primary-2 - ports: - - 28100:26379 - - oss-sentinel-primary-1: - logging: *logging - image: redis:5 - - oss-sentinel-primary-2: - logging: *logging - image: redis:5 - - # oss cluster (v7) - cluster-plain-creator-7: - logging: *logging - build: - context: ./rte/oss-cluster-7 - dockerfile: creator.Dockerfile - depends_on: - - master-plain-7-1 - - master-plain-7-2 - - master-plain-7-3 - master-plain-7-1: - logging: *logging - build: &cluster-plain-7-build ./rte/oss-cluster-7 - ports: - - 8200:6379 - networks: - default: - ipv4_address: 172.31.100.211 - ssh: - ipv4_address: 172.33.100.211 - master-plain-7-2: - logging: *logging - build: *cluster-plain-7-build - networks: - default: - ipv4_address: 172.31.100.212 - master-plain-7-3: - logging: *logging - build: *cluster-plain-7-build - networks: - default: - ipv4_address: 172.31.100.213 - - # oss cluster (v7) with rediserch > 2.2 - cluster-rs-creator-7: - logging: *logging - build: - context: &cluster-rs-7-build ./rte/oss-cluster-7-rs - dockerfile: creator.Dockerfile - depends_on: - - master-rs-7-1 - - master-rs-7-2 - - master-rs-7-3 - master-rs-7-1: - logging: *logging - build: *cluster-rs-7-build - ports: - - 8221:6379 - networks: - default: - ipv4_address: 172.31.100.221 - master-rs-7-2: - logging: *logging - build: *cluster-rs-7-build - networks: - default: - ipv4_address: 172.31.100.222 - master-rs-7-3: - logging: *logging - build: *cluster-rs-7-build - networks: - default: - ipv4_address: 172.31.100.223 - - # oss cluster with redisgears 2 - gears-cluster-2-0-creator: - logging: *logging - image: redis:latest - entrypoint: ['/bin/sh', '-c', 'redis-cli --cluster create 172.31.100.191:6379 172.31.100.192:6379 172.31.100.193:6379 172.31.100.194:6379 172.31.100.195:6379 172.31.100.196:6379 --cluster-replicas 1 --cluster-yes && tail -f /dev/null'] - depends_on: - - gears-cluster-2-0-node-1 - - gears-cluster-2-0-node-2 - - gears-cluster-2-0-node-3 - - gears-cluster-2-0-node-4 - - gears-cluster-2-0-node-5 - - gears-cluster-2-0-node-6 - gears-cluster-2-0-node-1: - logging: *logging - image: &gears-cluster-img redislabs/redisgears:edge - command: &gears-cluster-cmd redis-server --protected-mode no --loadmodule /build/target/release/libredisgears.so v8-plugin-path /build/target/release/libredisgears_v8_plugin.so --cluster-enabled yes - networks: - default: - ipv4_address: 172.31.100.191 - gears-cluster-2-0-node-2: - logging: *logging - image: *gears-cluster-img - command: *gears-cluster-cmd - networks: - default: - ipv4_address: 172.31.100.192 - gears-cluster-2-0-node-3: - logging: *logging - image: *gears-cluster-img - command: *gears-cluster-cmd - networks: - default: - ipv4_address: 172.31.100.193 - gears-cluster-2-0-node-4: - logging: *logging - image: *gears-cluster-img - command: *gears-cluster-cmd - networks: - default: - ipv4_address: 172.31.100.194 - gears-cluster-2-0-node-5: - logging: *logging - image: *gears-cluster-img - command: *gears-cluster-cmd - networks: - default: - ipv4_address: 172.31.100.195 - gears-cluster-2-0-node-6: - logging: *logging - image: *gears-cluster-img - command: *gears-cluster-cmd - networks: - default: - ipv4_address: 172.31.100.196 - - # redis enterprise - redis-enterprise: - logging: *logging - build: ./rte/redis-enterprise - cap_add: - - sys_resource - ports: - - 19443:9443 - - 12000:12000 -networks: - default: - name: "e2e-private-network" - ipam: - driver: default - config: - - subnet: 172.31.100.0/24 - gateway: 172.31.100.1 - ssh: - name: "e2e-ssh-network" - ipam: - driver: default - config: - - subnet: 172.33.100.0/24 - gateway: 172.33.100.1 From 3d0573e3db1ae90905290bba64165a022af098fa Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 4 Jun 2025 16:11:51 +0300 Subject: [PATCH 073/128] update environments --- tests/playwright/.desktop.env | 4 +- tests/playwright/.localChromium.env | 4 +- tests/playwright/fixtures/simple-slectron.ts | 7 +- tests/playwright/package.json | 13 +- tests/playwright/playwright.config.ts | 145 +++--- .../playwright/tests/example.electron.spec.ts | 52 ++- .../tests/simpleTests/demo-todo-app.spec.ts | 437 ------------------ .../tests/simpleTests/electronPw2.js | 67 --- tests/playwright/yarn.lock | 27 +- 9 files changed, 119 insertions(+), 637 deletions(-) delete mode 100644 tests/playwright/tests/simpleTests/demo-todo-app.spec.ts delete mode 100644 tests/playwright/tests/simpleTests/electronPw2.js diff --git a/tests/playwright/.desktop.env b/tests/playwright/.desktop.env index ba7c8f0112..6a22229192 100644 --- a/tests/playwright/.desktop.env +++ b/tests/playwright/.desktop.env @@ -1,5 +1,5 @@ -COMMON_URL=https://localhost:5530 -API_URL=https://localhost:5530/api +COMMON_URL=../../release/mac-arm64/Redis Insight.app/Contents/MacOS/Redis Insight +API_URL=https://localhost:5540/api OSS_SENTINEL_PASSWORD=password RI_APP_FOLDER_NAME=.redis-insight-stage diff --git a/tests/playwright/.localChromium.env b/tests/playwright/.localChromium.env index 9b2ba99e08..63c9d0f0da 100644 --- a/tests/playwright/.localChromium.env +++ b/tests/playwright/.localChromium.env @@ -1,2 +1,2 @@ -baseURL=https://localhost:5540 -apiUrl=https://localhost:5540/api +COMMON_URL=https://localhost:5540 +API_URL=https://localhost:5540/api diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts index dec8bf1deb..fbf960cb7f 100644 --- a/tests/playwright/fixtures/simple-slectron.ts +++ b/tests/playwright/fixtures/simple-slectron.ts @@ -1,14 +1,12 @@ +/* eslint-disable no-empty-pattern */ /* eslint-disable no-param-reassign */ /* eslint-disable no-restricted-syntax */ /* eslint-disable no-await-in-loop */ import { test as base, ElectronApplication, Page } from '@playwright/test' import { _electron as electron } from 'playwright' import log from 'node-color-log' -import * as dotenv from 'dotenv' -import path from 'node:path' import { ossStandaloneConfig } from '../helpers/conf' -dotenv.config({ path: path.resolve(__dirname, '..', '.desktop.env') }) // Define shared state for worker scope type WorkerSharedState = { apiUrl: string @@ -88,11 +86,12 @@ export const test = base.extend< { workerState: WorkerSharedState } >({ workerState: [ - async (_, use, testInfo) => { + async ({}, use, testInfo) => { log.info( `🚀 Setting up worker state for worker ${testInfo.workerIndex}`, ) const workerState: WorkerSharedState = { + // @ts-expect-error apiUrl: testInfo.project.use.apiUrl, dbConfig: ossStandaloneConfig, baseUrl: testInfo.project.use.baseURL || '', diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 6a01094a63..fc0bc556ad 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -6,19 +6,20 @@ "devDependencies": { "@faker-js/faker": "^9.6.0", "@playwright/test": "^1.51.0", - "@types/node": "^22.13.10", + "@types/node": "^22.15.29", "allure-commandline": "^2.33.0", "allure-js-commons": "^3.2.0", - "allure-playwright": "^3.2.0" + "allure-playwright": "^3.2.0", + "cross-env": "^7.0.3" }, "scripts": { "removeReportDirs": "rm -rf allure-results playwright-report test-results", "allTests": "playwright test", "generateReports": "allure generate --clean", - "test:chromium": "playwright test --project=localChromium", - "test:chromium-debug": "playwright test --project=localChromium --debug", - "test:electron": "playwright test --project=localElectron", - "test:electron-debug": "playwright test --project=localElectron", + "test:chromium": "cross-env envPath=.localChromium.env yarn playwright test --project=localChromium", + "test:chromium-debug": "cross-env envPath=.localChromium.env yarn playwright test --project=localChromium --debug", + "test:electron": "cross-env envPath=.desktop.env yarn playwright test --project=localElectron", + "test:electron-debug": "cross-env envPath=.desktop.env yarn playwright test --project=localElectron", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 55cb82e1e8..8e5d0429c9 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -1,19 +1,17 @@ -import {defineConfig, devices } from '@playwright/test' -import {Status} from 'allure-js-commons' -import * as os from 'node:os' +import { defineConfig, devices } from '@playwright/test' +import { Status } from 'allure-js-commons' +import dotenv from 'dotenv' +import * as os from 'os' + +dotenv.config({ + path: process.env.envPath ?? '.localChromium.env', + override: true, +}) export type TestOptions = { - apiUrl: string; + apiUrl: string } -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// import dotenv from 'dotenv'; -// import path from 'path'; -// dotenv.config({ path: path.resolve(__dirname, '.env') }); - /** * See https://playwright.dev/docs/test-configuration. */ @@ -26,7 +24,7 @@ export default defineConfig({ * Maximum time expect() should wait for the condition to be met. * For example in `await expect(locator).toHaveText();` */ - timeout: 5000 + timeout: 5000, }, /* Run tests in files in parallel */ fullyParallel: true, @@ -37,41 +35,46 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [['line'], ['html'], [ - 'allure-playwright', - { - resultsDir: 'allure-results', - detail: true, - suiteTitle: true, - links: { - issue: { - nameTemplate: 'Issue #%s', - urlTemplate: 'https://issues.example.com/%s', - }, - tms: { - nameTemplate: 'TMS #%s', - urlTemplate: 'https://tms.example.com/%s', - }, - jira: { - urlTemplate: (v: any) => `https://jira.example.com/browse/${v}`, + reporter: [ + ['line'], + ['html'], + [ + 'allure-playwright', + { + resultsDir: 'allure-results', + detail: true, + suiteTitle: true, + links: { + issue: { + nameTemplate: 'Issue #%s', + urlTemplate: 'https://issues.example.com/%s', + }, + tms: { + nameTemplate: 'TMS #%s', + urlTemplate: 'https://tms.example.com/%s', + }, + jira: { + urlTemplate: (v: any) => + `https://jira.example.com/browse/${v}`, + }, }, - }, - categories: [ - { - name: 'foo', - messageRegex: 'bar', - traceRegex: 'baz', - matchedStatuses: [Status.FAILED, Status.BROKEN], + categories: [ + { + name: 'foo', + messageRegex: 'bar', + traceRegex: 'baz', + matchedStatuses: [Status.FAILED, Status.BROKEN], + }, + ], + environmentInfo: { + os_platform: os.platform(), + os_release: os.release(), + os_version: os.version(), + node_version: process.version, }, - ], - environmentInfo: { - os_platform: os.platform(), - os_release: os.release(), - os_version: os.version(), - node_version: process.version, }, - }, - ]], + ], + ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { @@ -83,7 +86,7 @@ export default defineConfig({ testIdAttribute: 'data-testid', video: { mode: 'on', - size: {width: 1920, height: 1080} + size: { width: 1920, height: 1080 }, }, }, @@ -94,60 +97,32 @@ export default defineConfig({ testMatch: ['**.web.spec.ts'], use: { ...devices['Desktop Chrome'], - baseURL: process.env.COMMON_URL || 'https://localhost:5540', - apiUrl: process.env.API_URL || 'https://localhost:5540/api', + baseURL: process.env.COMMON_URL, + apiUrl: process.env.API_URL, headless: false, deviceScaleFactor: undefined, viewport: null, - launchOptions:{ - args: ['--start-maximized', + launchOptions: { + args: [ + '--start-maximized', '--disable-component-extensions-with-background-pages', '--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled', - '--ignore-certificate-errors' - ]} + '--ignore-certificate-errors', + ], + }, }, }, { name: 'localElectron', testMatch: ['**.electron.spec.ts'], use: { - baseURL: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', - apiUrl: 'https://localhost:5530/api', + baseURL: + process.env.COMMON_URL, + apiUrl: process.env.API_URL, headless: false, - }, }, - - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - // - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, ], /* Run your local dev server before starting the tests */ diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.electron.spec.ts index 05180ca2dd..f41f235f57 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.electron.spec.ts @@ -1,49 +1,53 @@ -import {test, expect} from '../fixtures/simple-slectron' -import {Common} from '../helpers/common' -import {BrowserPage} from '../pageObjects/browser-page' -import {DatabaseHelper} from "../helpers/database"; -import {APIKeyRequests} from "../helpers/api/api-keys"; -import {DatabaseAPIRequests} from "../helpers/api/api-databases"; +import { test, expect } from '../fixtures/simple-slectron' +import { Common } from '../helpers/common' +import { BrowserPage } from '../pageObjects/browser-page' +import { DatabaseHelper } from '../helpers/database' +import { APIKeyRequests } from '../helpers/api/api-keys' +import { DatabaseAPIRequests } from '../helpers/api/api-databases' let keyName: string let browserPage: BrowserPage let databaseHelper: DatabaseHelper -test.beforeEach(async ({electronPage, workerState}) => { - +test.beforeEach(async ({ electronPage, workerState }) => { // await electronPage.getByText('Add Redis').click() browserPage = new BrowserPage(electronPage) databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) - await databaseHelper.acceptLicenseTermsAndAddDatabaseApi(workerState.dbConfig, electronPage, workerState.apiUrl) + await databaseHelper.acceptLicenseTermsAndAddDatabaseApi( + workerState.dbConfig, + electronPage, + workerState.apiUrl, + ) keyName = Common.generateAlphanumeric(5) }) -test.afterEach(async ({electronApp, workerState}) => { - +test.afterEach(async ({ workerState }) => { const apiKeyClient = new APIKeyRequests(workerState.apiUrl) const dbApi = new DatabaseAPIRequests(workerState.apiUrl) - await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName, await browserPage.getWindowId()) - await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig, await browserPage.getWindowId()) + await apiKeyClient.deleteKeyByNameApi( + keyName, + workerState.dbConfig.databaseName, + await browserPage.getWindowId(), + ) + await dbApi.deleteStandaloneDatabaseApi( + workerState.dbConfig, + await browserPage.getWindowId(), + ) await workerState.electronApp.close() - }) -test('basic test', async ({workerState}) => { - - +test('basic test', async () => { await browserPage.addHashKey(keyName) // checks that the notification is displayed (should be in a different test) - await expect(await browserPage.page.getByText('Key has been added')).toBeVisible() - + await expect( + await browserPage.page.getByText('Key has been added'), + ).toBeVisible() // Check that new key is displayed in the list await browserPage.searchByKeyName(keyName) - const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) await expect(isKeyIsDisplayedInTheList).toBe(true) - - - }) - diff --git a/tests/playwright/tests/simpleTests/demo-todo-app.spec.ts b/tests/playwright/tests/simpleTests/demo-todo-app.spec.ts deleted file mode 100644 index 8641cb5f5d..0000000000 --- a/tests/playwright/tests/simpleTests/demo-todo-app.spec.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; - -test.beforeEach(async ({ page }) => { - await page.goto('https://demo.playwright.dev/todomvc'); -}); - -const TODO_ITEMS = [ - 'buy some cheese', - 'feed the cat', - 'book a doctors appointment' -] as const; - -test.describe('New Todo', () => { - test('should allow me to add todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create 1st todo. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Make sure the list only has one todo item. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0] - ]); - - // Create 2nd todo. - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - - // Make sure the list now has two todo items. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[1] - ]); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); - - test('should clear text input field when an item is added', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create one todo item. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Check that input is empty. - await expect(newTodo).toBeEmpty(); - await checkNumberOfTodosInLocalStorage(page, 1); - }); - - test('should append new items to the bottom of the list', async ({ page }) => { - // Create 3 items. - await createDefaultTodos(page); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - // Check test using different methods. - await expect(page.getByText('3 items left')).toBeVisible(); - await expect(todoCount).toHaveText('3 items left'); - await expect(todoCount).toContainText('3'); - await expect(todoCount).toHaveText(/3/); - - // Check all items in one call. - await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); - await checkNumberOfTodosInLocalStorage(page, 3); - }); -}); - -test.describe('Mark all as completed', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test.afterEach(async ({ page }) => { - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should allow me to mark all items as completed', async ({ page }) => { - // Complete all todos. - await page.getByLabel('Mark all as complete').check(); - - // Ensure all todos have 'completed' class. - await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - }); - - test('should allow me to clear the complete state of all items', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - // Check and then immediately uncheck. - await toggleAll.check(); - await toggleAll.uncheck(); - - // Should be no completed classes. - await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); - }); - - test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - await toggleAll.check(); - await expect(toggleAll).toBeChecked(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Uncheck first todo. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').uncheck(); - - // Reuse toggleAll locator and make sure its not checked. - await expect(toggleAll).not.toBeChecked(); - - await firstTodo.getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Assert the toggle all is checked again. - await expect(toggleAll).toBeChecked(); - }); -}); - -test.describe('Item', () => { - - test('should allow me to mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - // Check first item. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').check(); - await expect(firstTodo).toHaveClass('completed'); - - // Check second item. - const secondTodo = page.getByTestId('todo-item').nth(1); - await expect(secondTodo).not.toHaveClass('completed'); - await secondTodo.getByRole('checkbox').check(); - - // Assert completed class. - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).toHaveClass('completed'); - }); - - test('should allow me to un-mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const firstTodo = page.getByTestId('todo-item').nth(0); - const secondTodo = page.getByTestId('todo-item').nth(1); - const firstTodoCheckbox = firstTodo.getByRole('checkbox'); - - await firstTodoCheckbox.check(); - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await firstTodoCheckbox.uncheck(); - await expect(firstTodo).not.toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 0); - }); - - test('should allow me to edit an item', async ({ page }) => { - await createDefaultTodos(page); - - const todoItems = page.getByTestId('todo-item'); - const secondTodo = todoItems.nth(1); - await secondTodo.dblclick(); - await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); - await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); - - // Explicitly assert the new text value. - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2] - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); -}); - -test.describe('Editing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should hide other controls when editing', async ({ page }) => { - const todoItem = page.getByTestId('todo-item').nth(1); - await todoItem.dblclick(); - await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); - await expect(todoItem.locator('label', { - hasText: TODO_ITEMS[1], - })).not.toBeVisible(); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should save edits on blur', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should trim entered text', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should remove the item if an empty text string was entered', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[2], - ]); - }); - - test('should cancel edits on escape', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); - await expect(todoItems).toHaveText(TODO_ITEMS); - }); -}); - -test.describe('Counter', () => { - test('should display the current number of todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - await expect(todoCount).toContainText('1'); - - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - await expect(todoCount).toContainText('2'); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); -}); - -test.describe('Clear completed button', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - }); - - test('should display the correct text', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); - }); - - test('should remove completed items when clicked', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).getByRole('checkbox').check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(todoItems).toHaveCount(2); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should be hidden when there are no items that are completed', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); - }); -}); - -test.describe('Persistence', () => { - test('should persist its data', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const todoItems = page.getByTestId('todo-item'); - const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); - await firstTodoCheck.check(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - - // Ensure there is 1 completed item. - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - // Now reload. - await page.reload(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - }); -}); - -test.describe('Routing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - // make sure the app had a chance to save updated todos in storage - // before navigating to a new view, otherwise the items can get lost :( - // in some frameworks like Durandal - await checkTodosInLocalStorage(page, TODO_ITEMS[0]); - }); - - test('should allow me to display active items', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await expect(todoItem).toHaveCount(2); - await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should respect the back button', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await test.step('Showing all items', async () => { - await page.getByRole('link', { name: 'All' }).click(); - await expect(todoItem).toHaveCount(3); - }); - - await test.step('Showing active items', async () => { - await page.getByRole('link', { name: 'Active' }).click(); - }); - - await test.step('Showing completed items', async () => { - await page.getByRole('link', { name: 'Completed' }).click(); - }); - - await expect(todoItem).toHaveCount(1); - await page.goBack(); - await expect(todoItem).toHaveCount(2); - await page.goBack(); - await expect(todoItem).toHaveCount(3); - }); - - test('should allow me to display completed items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Completed' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(1); - }); - - test('should allow me to display all items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await page.getByRole('link', { name: 'Completed' }).click(); - await page.getByRole('link', { name: 'All' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(3); - }); - - test('should highlight the currently applied filter', async ({ page }) => { - await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); - - //create locators for active and completed links - const activeLink = page.getByRole('link', { name: 'Active' }); - const completedLink = page.getByRole('link', { name: 'Completed' }); - await activeLink.click(); - - // Page change - active items. - await expect(activeLink).toHaveClass('selected'); - await completedLink.click(); - - // Page change - completed items. - await expect(completedLink).toHaveClass('selected'); - }); -}); - -async function createDefaultTodos(page: Page) { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } -} - -async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).length === e; - }, expected); -} - -async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; - }, expected); -} - -async function checkTodosInLocalStorage(page: Page, title: string) { - return await page.waitForFunction(t => { - return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); - }, title); -} diff --git a/tests/playwright/tests/simpleTests/electronPw2.js b/tests/playwright/tests/simpleTests/electronPw2.js deleted file mode 100644 index fbd796c265..0000000000 --- a/tests/playwright/tests/simpleTests/electronPw2.js +++ /dev/null @@ -1,67 +0,0 @@ -const { _electron: electron } = require('@playwright/test'); -/** - * test that doesn't involve anything else than playwright meant for simple demonstration - */ -(async () => { - console.log("🚀 Starting RedisInsight..."); - - const electronApp = await electron.launch({ - executablePath: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', // CAHNGE - args: ['index.html'], - // env: { DEBUG: 'pw:electron*' }, // Enable debug logs if needed - timeout: 60000, // Max wait time - }); - - - console.log("⏳ Waiting for window..."); - - - let windows = []; - let elapsedTime = 0; - const maxWaitTime = 60000; // 60 seconds - const interval = 2000; // Check every 2 seconds - - // Wait for a window to appear - while (windows.length === 0 && elapsedTime < maxWaitTime) { - await new Promise((resolve) => setTimeout(resolve, interval)); // Wait 2s - windows = await electronApp.windows(); // Check for open windows - elapsedTime += interval; - console.log(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`); - - if (windows.length > 0) { - console.log(`✅ Found ${windows.length} window(s)!`); - break; - } - } - ; - - if (windows.length === 0) { - console.error("❌ No windows detected after 60s! Exiting."); - await electronApp.close(); - return; - } - - const window = windows[0]; // Pick the first window - await window.waitForLoadState('domcontentloaded'); // Ensure it's fully loaded - - // ✅ Print the window title - const title = await window.title(); - console.log(`🖥️ Window Title: ${title}`); - - - // ✅ Click an element containing specific text - const textToClick = "Add Redis Database"; // Change this to the actual text - try { - await window.getByText(textToClick).click(); - console.log(`🖱️ Clicked on "${textToClick}" successfully!`); - } catch (error) { - console.error(`❌ Could not find or click on "${textToClick}".`, error); - } - await window.reload(); - // ✅ Capture a screenshot - await window.screenshot({ path: 'screenshot.png' }); - console.log("📸 Screenshot captured!"); - - // Exit the app. - await electronApp.close(); -})(); diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index 4044561bda..81efc7dc1d 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -40,12 +40,12 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@types/node@^22.13.10": - version "22.13.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" - integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw== +"@types/node@^22.15.29": + version "22.15.29" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.29.tgz#c75999124a8224a3f79dd8b6ccfb37d74098f678" + integrity sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ== dependencies: - undici-types "~6.20.0" + undici-types "~6.21.0" abbrev@1: version "1.1.1" @@ -212,7 +212,14 @@ console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -cross-spawn@^7.0.6: +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -964,10 +971,10 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== unique-filename@^1.1.1: version "1.1.1" From 9f9eb14197782f4f95d278a7d5273b6ebd937ac2 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 4 Jun 2025 18:58:25 +0300 Subject: [PATCH 074/128] update client config --- tests/playwright/.desktop.env | 3 +- tests/playwright/helpers/api/http-client.ts | 49 +++++++++++---------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/tests/playwright/.desktop.env b/tests/playwright/.desktop.env index 6a22229192..0cfc582975 100644 --- a/tests/playwright/.desktop.env +++ b/tests/playwright/.desktop.env @@ -1,5 +1,6 @@ COMMON_URL=../../release/mac-arm64/Redis Insight.app/Contents/MacOS/Redis Insight -API_URL=https://localhost:5540/api +API_URL=http://localhost:5530/api + OSS_SENTINEL_PASSWORD=password RI_APP_FOLDER_NAME=.redis-insight-stage diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index 37050b3513..f035b073ed 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -1,49 +1,50 @@ -import axios, { AxiosInstance } from 'axios'; - +import axios, { AxiosInstance } from 'axios' +import https from 'https' interface CustomAxiosInstance extends AxiosInstance { - setHeaders: (headers: Record) => void; + setHeaders: (headers: Record) => void } export class HttpClient { - private apiUrl: string; - private apiClient: CustomAxiosInstance; + private apiUrl: string + + private apiClient: CustomAxiosInstance - constructor(apiUrl: string, ) { - this.apiUrl = apiUrl; + constructor(apiUrl: string) { + this.apiUrl = apiUrl this.apiClient = axios.create({ baseURL: this.apiUrl, headers: {}, - httpsAgent: new (require('https').Agent)({ + httpsAgent: new https.Agent({ rejectUnauthorized: false, // Allows self-signed/invalid SSL certs }), - }) as CustomAxiosInstance; + }) as CustomAxiosInstance // Attach setHeaders method to allow setting headers dynamically this.apiClient.setHeaders = (headers: Record) => { - Object.assign(this.apiClient.defaults.headers.common, headers); - }; + Object.assign(this.apiClient.defaults.headers.common, headers) + } // Enable logging if DEBUG is set if (process.env.DEBUG) { - this.apiClient.interceptors.request.use(request => { - console.log('Starting Request', request); - return request; - }); + this.apiClient.interceptors.request.use((request) => { + console.log('Starting Request', request) + return request + }) this.apiClient.interceptors.response.use( - response => { - console.log('Response:', response); - return response; + (response) => { + console.log('Response:', response) + return response + }, + (error) => { + console.error('Error Response:', error.response) + return Promise.reject(error) }, - error => { - console.error('Error Response:', error.response); - return Promise.reject(error); - } - ); + ) } } getClient(): CustomAxiosInstance { - return this.apiClient; + return this.apiClient } } From 1b3000570db6dd71f721099e1d617033d46ea7af Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 4 Jun 2025 20:31:26 +0300 Subject: [PATCH 075/128] update configs --- tests/playwright/.localChromium.env | 2 - tests/playwright/README.md | 4 +- tests/playwright/{ => env}/.desktop.env | 3 +- tests/playwright/env/.docker.env | 47 +++++++ tests/playwright/env/.local-web.env | 47 +++++++ tests/playwright/helpers/conf.ts | 157 ++++++++++++++---------- tests/playwright/package.json | 14 ++- tests/playwright/playwright.config.ts | 2 +- 8 files changed, 201 insertions(+), 75 deletions(-) delete mode 100644 tests/playwright/.localChromium.env rename tests/playwright/{ => env}/.desktop.env (99%) create mode 100644 tests/playwright/env/.docker.env create mode 100644 tests/playwright/env/.local-web.env diff --git a/tests/playwright/.localChromium.env b/tests/playwright/.localChromium.env deleted file mode 100644 index 63c9d0f0da..0000000000 --- a/tests/playwright/.localChromium.env +++ /dev/null @@ -1,2 +0,0 @@ -COMMON_URL=https://localhost:5540 -API_URL=https://localhost:5540/api diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 3fdfa48cba..1f0f1c3c58 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -107,7 +107,7 @@ Runs the end-to-end tests (all projects): Runs the tests only on Desktop Chrome. ```shell - yarn test:chromium + yarn test:chromium:docker ``` To run Electron tests: @@ -120,7 +120,7 @@ To run Electron tests: Runs the tests in framework debug mode. ``` -yarn playwright test --project=localChromium --debug +yarn playwright test --project=DockerBuild --debug ``` Runs the tests in a specific file: diff --git a/tests/playwright/.desktop.env b/tests/playwright/env/.desktop.env similarity index 99% rename from tests/playwright/.desktop.env rename to tests/playwright/env/.desktop.env index 0cfc582975..001833aa31 100644 --- a/tests/playwright/.desktop.env +++ b/tests/playwright/env/.desktop.env @@ -1,7 +1,6 @@ COMMON_URL=../../release/mac-arm64/Redis Insight.app/Contents/MacOS/Redis Insight API_URL=http://localhost:5530/api -OSS_SENTINEL_PASSWORD=password RI_APP_FOLDER_NAME=.redis-insight-stage OSS_STANDALONE_HOST=localhost @@ -42,6 +41,7 @@ OSS_CLUSTER_PORT=8200 OSS_SENTINEL_HOST=localhost OSS_SENTINEL_PORT=28100 +OSS_SENTINEL_PASSWORD=password RE_CLUSTER_HOST=localhost RE_CLUSTER_PORT=19443 @@ -51,4 +51,5 @@ RI_NOTIFICATION_SYNC_INTERVAL=30000 RI_FEATURES_CONFIG_URL=http://localhost:5551/remote/features-config.json RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 + REMOTE_FOLDER_PATH=/home/runner/work/RedisInsight/RedisInsight/tests/e2e/remote diff --git a/tests/playwright/env/.docker.env b/tests/playwright/env/.docker.env new file mode 100644 index 0000000000..4cdc9c6d5a --- /dev/null +++ b/tests/playwright/env/.docker.env @@ -0,0 +1,47 @@ +COMMON_URL=https://localhost:5540 +API_URL=https://localhost:5540/api + +RI_APP_FOLDER_NAME=.redis-insight + +OSS_STANDALONE_HOST=host.docker.internal +OSS_STANDALONE_PORT=8100 + +OSS_STANDALONE_V5_HOST=host.docker.internal +OSS_STANDALONE_V5_PORT=8101 + +OSS_STANDALONE_V7_HOST=host.docker.internal +OSS_STANDALONE_V7_PORT=8108 + +OSS_STANDALONE_V8_HOST=host.docker.internal +OSS_STANDALONE_V8_PORT=8109 + +OSS_STANDALONE_REDISEARCH_HOST=host.docker.internal +OSS_STANDALONE_REDISEARCH_PORT=8102 + +OSS_STANDALONE_BIG_HOST=host.docker.internal +OSS_STANDALONE_BIG_PORT=8103 + +OSS_STANDALONE_TLS_HOST=host.docker.internal +OSS_STANDALONE_TLS_PORT=8104 + +OSS_STANDALONE_EMPTY_HOST=host.docker.internal +OSS_STANDALONE_EMPTY_PORT=8105 + +OSS_STANDALONE_REDISGEARS_HOST=host.docker.internal +OSS_STANDALONE_REDISGEARS_PORT=8106 + +OSS_STANDALONE_NOPERM_HOST=host.docker.internal +OSS_STANDALONE_NOPERM_PORT=8100 + +OSS_CLUSTER_REDISGEARS_2_HOST=host.docker.internal +OSS_CLUSTER_REDISGEARS_2_PORT=8107 + +OSS_CLUSTER_HOST=host.docker.internal +OSS_CLUSTER_PORT=8200 + +OSS_SENTINEL_HOST=host.docker.internal +OSS_SENTINEL_PORT=28100 +OSS_SENTINEL_PASSWORD=password + +RE_CLUSTER_HOST=host.docker.internal +RE_CLUSTER_PORT=19443 diff --git a/tests/playwright/env/.local-web.env b/tests/playwright/env/.local-web.env new file mode 100644 index 0000000000..d7baae78ee --- /dev/null +++ b/tests/playwright/env/.local-web.env @@ -0,0 +1,47 @@ +COMMON_URL=http://localhost:8080 +API_URL=http://localhost:5540/api + +RI_APP_FOLDER_NAME=.redis-insight + +OSS_STANDALONE_HOST=localhost +OSS_STANDALONE_PORT=8100 + +OSS_STANDALONE_V5_HOST=localhost +OSS_STANDALONE_V5_PORT=8101 + +OSS_STANDALONE_V7_HOST=localhost +OSS_STANDALONE_V7_PORT=8108 + +OSS_STANDALONE_V8_HOST=localhost +OSS_STANDALONE_V8_PORT=8109 + +OSS_STANDALONE_REDISEARCH_HOST=localhost +OSS_STANDALONE_REDISEARCH_PORT=8102 + +OSS_STANDALONE_BIG_HOST=localhost +OSS_STANDALONE_BIG_PORT=8103 + +OSS_STANDALONE_TLS_HOST=localhost +OSS_STANDALONE_TLS_PORT=8104 + +OSS_STANDALONE_EMPTY_HOST=localhost +OSS_STANDALONE_EMPTY_PORT=8105 + +OSS_STANDALONE_REDISGEARS_HOST=localhost +OSS_STANDALONE_REDISGEARS_PORT=8106 + +OSS_STANDALONE_NOPERM_HOST=localhost +OSS_STANDALONE_NOPERM_PORT=8100 + +OSS_CLUSTER_REDISGEARS_2_HOST=localhost +OSS_CLUSTER_REDISGEARS_2_PORT=8107 + +OSS_CLUSTER_HOST=localhost +OSS_CLUSTER_PORT=8200 + +OSS_SENTINEL_HOST=localhost +OSS_SENTINEL_PORT=28100 +OSS_SENTINEL_PASSWORD=password + +RE_CLUSTER_HOST=localhost +RE_CLUSTER_PORT=19443 diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts index f55b70da59..e4c69740a5 100644 --- a/tests/playwright/helpers/conf.ts +++ b/tests/playwright/helpers/conf.ts @@ -1,4 +1,4 @@ -import {faker} from '@faker-js/faker' +import { faker } from '@faker-js/faker' import * as os from 'os' import * as fs from 'fs' import { join as joinPath } from 'path' @@ -12,90 +12,93 @@ export const googleUserPassword = process.env.GOOGLE_USER_PASSWORD || '' export const samlUser = process.env.E2E_SSO_EMAIL || '' export const samlUserPassword = process.env.E2E_SSO_PASSWORD || '' -export const workingDirectory = process.env.RI_APP_FOLDER_ABSOLUTE_PATH - || (joinPath(os.homedir(), process.env.RI_APP_FOLDER_NAME || '.redis-insight')) +export const workingDirectory = + process.env.RI_APP_FOLDER_ABSOLUTE_PATH || + joinPath(os.homedir(), process.env.RI_APP_FOLDER_NAME || '.redis-insight') export const fileDownloadPath = joinPath(os.homedir(), 'Downloads') const uniqueId = faker.string.alphanumeric({ length: 10 }) export const ossStandaloneConfig = { - host: process.env.OSS_STANDALONE_HOST || 'oss-standalone-v8', - port: process.env.OSS_STANDALONE_PORT || '6379', + host: process.env.OSS_STANDALONE_HOST, + port: process.env.OSS_STANDALONE_PORT, databaseName: `${process.env.OSS_STANDALONE_DATABASE_NAME || 'test_standalone'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_USERNAME, - databasePassword: process.env.OSS_STANDALONE_PASSWORD + databasePassword: process.env.OSS_STANDALONE_PASSWORD, } export const ossStandaloneConfigEmpty = { - host: process.env.OSS_STANDALONE_EMPTY_HOST || 'oss-standalone-empty', - port: process.env.OSS_STANDALONE_EMPTY_PORT || '6379', + host: process.env.OSS_STANDALONE_EMPTY_HOST, + port: process.env.OSS_STANDALONE_EMPTY_PORT, databaseName: `${process.env.OSS_STANDALONE_EMPTY_DATABASE_NAME || 'test_standalone_empty'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_EMPTY_USERNAME, - databasePassword: process.env.OSS_STANDALONE_EMPTY_PASSWORD + databasePassword: process.env.OSS_STANDALONE_EMPTY_PASSWORD, } export const ossStandaloneV5Config = { - host: process.env.OSS_STANDALONE_V5_HOST || 'oss-standalone-v5', - port: process.env.OSS_STANDALONE_V5_PORT || '6379', + host: process.env.OSS_STANDALONE_V5_HOST, + port: process.env.OSS_STANDALONE_V5_PORT, databaseName: `${process.env.OSS_STANDALONE_V5_DATABASE_NAME || 'test_standalone-v5'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_V5_USERNAME, - databasePassword: process.env.OSS_STANDALONE_V5_PASSWORD + databasePassword: process.env.OSS_STANDALONE_V5_PASSWORD, } export const ossStandaloneV7Config = { - host: process.env.OSS_STANDALONE_V7_HOST || 'oss-standalone-v7', - port: process.env.OSS_STANDALONE_V7_PORT || '6379', + host: process.env.OSS_STANDALONE_V7_HOST, + port: process.env.OSS_STANDALONE_V7_PORT, databaseName: `${process.env.OSS_STANDALONE_V7_DATABASE_NAME || 'test_standalone-v7'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_V7_USERNAME, - databasePassword: process.env.OSS_STANDALONE_V7_PASSWORD + databasePassword: process.env.OSS_STANDALONE_V7_PASSWORD, } export const ossStandaloneV6Config = { - host: process.env.OSS_STANDALONE_V8_HOST || 'oss-standalone', - port: process.env.OSS_STANDALONE_V8_PORT || '6379', + host: process.env.OSS_STANDALONE_V8_HOST, + port: process.env.OSS_STANDALONE_V8_PORT, databaseName: `${process.env.OSS_STANDALONE_V8_DATABASE_NAME || 'test_standalone-v6'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_V8_USERNAME, - databasePassword: process.env.OSS_STANDALONE_V8_PASSWORD + databasePassword: process.env.OSS_STANDALONE_V8_PASSWORD, } export const ossStandaloneRedisearch = { - host: process.env.OSS_STANDALONE_REDISEARCH_HOST || 'oss-standalone-redisearch', - port: process.env.OSS_STANDALONE_REDISEARCH_PORT || '6379', + host: process.env.OSS_STANDALONE_REDISEARCH_HOST, + port: process.env.OSS_STANDALONE_REDISEARCH_PORT, databaseName: `${process.env.OSS_STANDALONE_REDISEARCH_DATABASE_NAME || 'test_standalone-redisearch'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_REDISEARCH_USERNAME, - databasePassword: process.env.OSS_STANDALONE_REDISEARCH_PASSWORD + databasePassword: process.env.OSS_STANDALONE_REDISEARCH_PASSWORD, } export const ossClusterConfig = { - ossClusterHost: process.env.OSS_CLUSTER_HOST || 'master-plain-7-1', - ossClusterPort: process.env.OSS_CLUSTER_PORT || '6379', - ossClusterDatabaseName: `${process.env.OSS_CLUSTER_DATABASE_NAME || 'test_cluster'}-${uniqueId}` + ossClusterHost: process.env.OSS_CLUSTER_HOST, + ossClusterPort: process.env.OSS_CLUSTER_PORT, + ossClusterDatabaseName: `${process.env.OSS_CLUSTER_DATABASE_NAME || 'test_cluster'}-${uniqueId}`, } export const ossSentinelConfig = { - sentinelHost: process.env.OSS_SENTINEL_HOST || 'oss-sentinel', - sentinelPort: process.env.OSS_SENTINEL_PORT || '26379', - sentinelPassword: process.env.OSS_SENTINEL_PASSWORD || 'password', - masters: [{ - alias: `primary-group-1}-${uniqueId}`, - db: '0', - name: 'primary-group-1', - password: 'defaultpass' - }, - { - alias: `primary-group-2}-${uniqueId}`, - db: '0', - name: 'primary-group-2', - password: 'defaultpass' - }], - name: ['primary-group-1', 'primary-group-2'] + sentinelHost: process.env.OSS_SENTINEL_HOST, + sentinelPort: process.env.OSS_SENTINEL_PORT, + sentinelPassword: process.env.OSS_SENTINEL_PASSWORD, + masters: [ + { + alias: `primary-group-1}-${uniqueId}`, + db: '0', + name: 'primary-group-1', + password: 'defaultpass', + }, + { + alias: `primary-group-2}-${uniqueId}`, + db: '0', + name: 'primary-group-2', + password: 'defaultpass', + }, + ], + name: ['primary-group-1', 'primary-group-2'], } export const redisEnterpriseClusterConfig = { - host: process.env.RE_CLUSTER_HOST || 'redis-enterprise', - port: process.env.RE_CLUSTER_PORT || '9443', + host: process.env.RE_CLUSTER_HOST, + port: process.env.RE_CLUSTER_PORT, databaseName: process.env.RE_CLUSTER_DATABASE_NAME || 'test-re-standalone', databaseUsername: process.env.RE_CLUSTER_ADMIN_USER || 'demo@redislabs.com', - databasePassword: process.env.RE_CLUSTER_ADMIN_PASSWORD || '123456' + databasePassword: process.env.RE_CLUSTER_ADMIN_PASSWORD || '123456', } export const invalidOssStandaloneConfig = { @@ -103,15 +106,15 @@ export const invalidOssStandaloneConfig = { port: '1010', databaseName: `${process.env.OSS_STANDALONE_INVALID_DATABASE_NAME || 'test_standalone-invalid'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_INVALID_USERNAME, - databasePassword: process.env.OSS_STANDALONE_INVALID_PASSWORD + databasePassword: process.env.OSS_STANDALONE_INVALID_PASSWORD, } export const ossStandaloneBigConfig = { - host: process.env.OSS_STANDALONE_BIG_HOST || 'oss-standalone-big', - port: process.env.OSS_STANDALONE_BIG_PORT || '6379', + host: process.env.OSS_STANDALONE_BIG_HOST, + port: process.env.OSS_STANDALONE_BIG_PORT, databaseName: `${process.env.OSS_STANDALONE_BIG_DATABASE_NAME || 'test_standalone_big'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_BIG_USERNAME, - databasePassword: process.env.OSS_STANDALONE_BIG_PASSWORD + databasePassword: process.env.OSS_STANDALONE_BIG_PASSWORD, } export const cloudDatabaseConfig = { @@ -121,15 +124,15 @@ export const cloudDatabaseConfig = { databaseUsername: process.env.E2E_CLOUD_DATABASE_USERNAME, databasePassword: process.env.E2E_CLOUD_DATABASE_PASSWORD, accessKey: process.env.E2E_CLOUD_API_ACCESS_KEY || '', - secretKey: process.env.E2E_CLOUD_API_SECRET_KEY || '' + secretKey: process.env.E2E_CLOUD_API_SECRET_KEY || '', } export const ossStandaloneNoPermissionsConfig = { - host: process.env.OSS_STANDALONE_NOPERM_HOST || 'oss-standalone', - port: process.env.OSS_STANDALONE_NOPERM_PORT || '6379', + host: process.env.OSS_STANDALONE_NOPERM_HOST, + port: process.env.OSS_STANDALONE_NOPERM_PORT, databaseName: `${process.env.OSS_STANDALONE_NOPERM_DATABASE_NAME || 'oss-standalone-no-permissions'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_NOPERM_USERNAME || 'noperm', - databasePassword: process.env.OSS_STANDALONE_NOPERM_PASSWORD + databasePassword: process.env.OSS_STANDALONE_NOPERM_PASSWORD, } export const ossStandaloneForSSHConfig = { @@ -137,7 +140,7 @@ export const ossStandaloneForSSHConfig = { port: process.env.OSS_STANDALONE_SSH_PORT || '6379', databaseName: `${process.env.OSS_STANDALONE_SSH_DATABASE_NAME || 'oss-standalone-for-ssh'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_SSH_USERNAME, - databasePassword: process.env.OSS_STANDALONE_SSH_PASSWORD + databasePassword: process.env.OSS_STANDALONE_SSH_PASSWORD, } export const ossClusterForSSHConfig = { @@ -145,36 +148,60 @@ export const ossClusterForSSHConfig = { port: process.env.OSS_CLUSTER_SSH_PORT || '6379', databaseName: `${process.env.OSS_CLUSTER_SSH_DATABASE_NAME || 'oss-cluster-for-ssh'}-${uniqueId}`, databaseUsername: process.env.OSS_CLUSTER_SSH_USERNAME, - databasePassword: process.env.OSS_CLUSTER_SSH_PASSWORD + databasePassword: process.env.OSS_CLUSTER_SSH_PASSWORD, } export const ossStandaloneTlsConfig = { - host: process.env.OSS_STANDALONE_TLS_HOST || 'oss-standalone-tls', - port: process.env.OSS_STANDALONE_TLS_PORT || '6379', + host: process.env.OSS_STANDALONE_TLS_HOST, + port: process.env.OSS_STANDALONE_TLS_PORT, databaseName: `${process.env.OSS_STANDALONE_TLS_DATABASE_NAME || 'test_standalone_tls'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_TLS_USERNAME, databasePassword: process.env.OSS_STANDALONE_TLS_PASSWORD, caCert: { name: `ca}-${uniqueId}`, - certificate: process.env.E2E_CA_CRT || fs.readFileSync(path.resolve(__dirname, '../../e2e/rte/oss-standalone-tls/certs/redisCA.crt'), 'utf-8') + certificate: + process.env.E2E_CA_CRT || + fs.readFileSync( + path.resolve( + __dirname, + '../../e2e/rte/oss-standalone-tls/certs/redisCA.crt', + ), + 'utf-8', + ), }, clientCert: { name: `client}-${uniqueId}`, - certificate: process.env.E2E_CLIENT_CRT || fs.readFileSync(path.resolve(__dirname, '../../e2e/rte/oss-standalone-tls/certs/redis.crt'), 'utf-8'), - key: process.env.E2E_CLIENT_KEY || fs.readFileSync(path.resolve(__dirname, '../../e2e/rte/oss-standalone-tls/certs/redis.key'), 'utf-8') - } + certificate: + process.env.E2E_CLIENT_CRT || + fs.readFileSync( + path.resolve( + __dirname, + '../../e2e/rte/oss-standalone-tls/certs/redis.crt', + ), + 'utf-8', + ), + key: + process.env.E2E_CLIENT_KEY || + fs.readFileSync( + path.resolve( + __dirname, + '../../e2e/rte/oss-standalone-tls/certs/redis.key', + ), + 'utf-8', + ), + }, } export const ossStandaloneRedisGears = { - host: process.env.OSS_STANDALONE_REDISGEARS_HOST || 'oss-standalone-redisgears-2-0', - port: process.env.OSS_STANDALONE_REDISGEARS_PORT || '6379', + host: process.env.OSS_STANDALONE_REDISGEARS_HOST, + port: process.env.OSS_STANDALONE_REDISGEARS_PORT, databaseName: `${process.env.OSS_STANDALONE_REDISGEARS_DATABASE_NAME || 'test_standalone_redisgears'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_REDISGEARS_USERNAME, - databasePassword: process.env.OSS_STANDALONE_REDISGEARS_PASSWORD + databasePassword: process.env.OSS_STANDALONE_REDISGEARS_PASSWORD, } export const ossClusterRedisGears = { - ossClusterHost: process.env.OSS_CLUSTER_REDISGEARS_2_HOST || 'gears-cluster-2-0-node-1', - ossClusterPort: process.env.OSS_CLUSTER_REDISGEARS_2_PORT || '6379', - ossClusterDatabaseName: `${process.env.OSS_CLUSTER_REDISGEARS_2_NAME || 'test_cluster-gears-2.0'}-${uniqueId}` + ossClusterHost: process.env.OSS_CLUSTER_REDISGEARS_2_HOST, + ossClusterPort: process.env.OSS_CLUSTER_REDISGEARS_2_PORT, + ossClusterDatabaseName: `${process.env.OSS_CLUSTER_REDISGEARS_2_NAME || 'test_cluster-gears-2.0'}-${uniqueId}`, } diff --git a/tests/playwright/package.json b/tests/playwright/package.json index fc0bc556ad..0d76dc169c 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -16,10 +16,16 @@ "removeReportDirs": "rm -rf allure-results playwright-report test-results", "allTests": "playwright test", "generateReports": "allure generate --clean", - "test:chromium": "cross-env envPath=.localChromium.env yarn playwright test --project=localChromium", - "test:chromium-debug": "cross-env envPath=.localChromium.env yarn playwright test --project=localChromium --debug", - "test:electron": "cross-env envPath=.desktop.env yarn playwright test --project=localElectron", - "test:electron-debug": "cross-env envPath=.desktop.env yarn playwright test --project=localElectron", + + "test:chromium:docker": "cross-env envPath=env/.docker.env yarn playwright test --project=localChromium", + "test:chromium:docker:debug": "yarn test:chromium:docker --debug", + + "test:chromium:local-web": "cross-env envPath=env/.local-web.env yarn playwright test --project=localChromium", + "test:chromium:local-web:debug": "yarn test:chromium:local-web --debug", + + "test:electron": "cross-env envPath=env/.desktop.env yarn playwright test --project=localElectron", + "test:electron:debug": "yarn test:electron --debug", + "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 8e5d0429c9..9c30cc5331 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv' import * as os from 'os' dotenv.config({ - path: process.env.envPath ?? '.localChromium.env', + path: process.env.envPath ?? 'env/.local-web.env', override: true, }) From 9634d677916f64e46a0831747d59eef4fd86c4bd Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Wed, 4 Jun 2025 20:38:36 +0300 Subject: [PATCH 076/128] remove differences --- tests/e2e/helpers/database.ts | 1 - .../tests/web/regression/insights/live-recommendations.e2e.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/e2e/helpers/database.ts b/tests/e2e/helpers/database.ts index edc7dc1e97..a59439542c 100644 --- a/tests/e2e/helpers/database.ts +++ b/tests/e2e/helpers/database.ts @@ -208,7 +208,6 @@ export class DatabaseHelper { ); // Reload Page to see the new added database through api await myRedisDatabasePage.reloadPage(); - // Connect to DB await myRedisDatabasePage.clickOnDBByName( databaseParameters.databaseName! diff --git a/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts b/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts index 439d57d790..ec63f56d81 100644 --- a/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts +++ b/tests/e2e/tests/web/regression/insights/live-recommendations.e2e.ts @@ -44,8 +44,7 @@ const setPasswordRecom = RecommendationIds.setPassword; fixture `Live Recommendations` .meta({ type: 'regression', rte: rte.standalone }) .page(commonUrl) - .beforeEach(async(t) => { - await t.debug() + .beforeEach(async() => { await databaseHelper.acceptLicenseTerms(); await refreshFeaturesTestData(); await modifyFeaturesConfigJson(featuresConfig); From 3f93bb83f6272be0cc94e0142fb8a621111bbbc1 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Thu, 5 Jun 2025 11:02:45 +0300 Subject: [PATCH 077/128] update workflows --- .github/workflows/tests-e2e-playwright.yml | 85 +++++++++++++++++++ .github/workflows/tests.yml | 9 +- tests/e2e/docker.web.docker-compose.yml | 2 + .../.github/workflows/playwright.yml | 27 ------ tests/playwright/package.json | 7 +- tests/playwright/yarn.lock | 14 +++ 6 files changed, 111 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/tests-e2e-playwright.yml delete mode 100644 tests/playwright/.github/workflows/playwright.yml diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml new file mode 100644 index 0000000000..9a7c4009a3 --- /dev/null +++ b/.github/workflows/tests-e2e-playwright.yml @@ -0,0 +1,85 @@ +name: Playwright E2E Tests +on: + # push: + # branches: + # - main + # pull_request: + # branches: + # - main + workflow_call: + +env: + E2E_CLOUD_DATABASE_USERNAME: ${{ secrets.E2E_CLOUD_DATABASE_USERNAME }} + E2E_CLOUD_DATABASE_PASSWORD: ${{ secrets.E2E_CLOUD_DATABASE_PASSWORD }} + E2E_CLOUD_API_ACCESS_KEY: ${{ secrets.E2E_CLOUD_API_ACCESS_KEY }} + E2E_CLOUD_DATABASE_HOST: ${{ secrets.E2E_CLOUD_DATABASE_HOST }} + E2E_CLOUD_DATABASE_PORT: ${{ secrets.E2E_CLOUD_DATABASE_PORT }} + E2E_CLOUD_DATABASE_NAME: ${{ secrets.E2E_CLOUD_DATABASE_NAME }} + E2E_CLOUD_API_SECRET_KEY: ${{ secrets.E2E_CLOUD_API_SECRET_KEY }} + + E2E_RI_ENCRYPTION_KEY: ${{ secrets.E2E_RI_ENCRYPTION_KEY }} + RI_ENCRYPTION_KEY: ${{ secrets.RI_ENCRYPTION_KEY }} + RI_SERVER_TLS_CERT: ${{ secrets.RI_SERVER_TLS_CERT }} + RI_SERVER_TLS_KEY: ${{ secrets.RI_SERVER_TLS_KEY }} + TEST_BIG_DB_DUMP: ${{ secrets.TEST_BIG_DB_DUMP }} + E2E_VOLUME_PATH: '/usr/src/app' + +jobs: + e2e-playwright-chromium-docker: + name: E2E Playwright Chromium Docker Build Tests + timeout-minutes: 60 + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/install-all-build-libs + + - name: Install dependencies for Playwright tests + uses: ./.github/actions/install-deps + with: + dir-path: './tests/playwright' + + # - name: Install Playwright Browsers + # working-directory: ./tests/playwright + # run: yarn playwright install --with-deps + + - name: Download Docker Artifacts + uses: actions/download-artifact@v4 + with: + name: docker-builds + path: ./release + + - name: Load built docker image from workspace + run: | + docker image load -i ./release/docker/docker-linux-alpine.amd64.tar + + - name: Set up redis test environments + run: | + TEST_BIG_DB_DUMP=$TEST_BIG_DB_DUMP \ + docker compose -p e2e-rte \ + -f tests/e2e/rte.docker-compose.yml \ + up --abort-on-container-exit --force-recreate + + - name: Set up RI docker image + run: | + E2E_RI_ENCRYPTION_KEY="$E2E_RI_ENCRYPTION_KEY" \ + RI_SERVER_TLS_CERT="$RI_SERVER_TLS_CERT" \ + RI_SERVER_TLS_KEY="$RI_SERVER_TLS_KEY" \ + docker compose -p e2e-ri-docker \ + -f tests/e2e/docker.web.docker-compose.yml \ + up --abort-on-container-exit --force-recreate + + - name: Run Playwright tests + timeout-minutes: 80 + working-directory: ./tests/playwright + if: ${{ !cancelled() }} + run: | + yarn test:chromium:docker + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7040d652c8..a727764ab2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -138,7 +138,7 @@ jobs: e2e-approve: runs-on: ubuntu-latest needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') + # if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') timeout-minutes: 60 environment: ${{ startsWith(github.ref_name, 'e2e/') && 'e2e-approve' || 'staging' }} name: Approve E2E tests @@ -161,6 +161,13 @@ jobs: with: debug: ${{ inputs.debug || false }} + tests-e2e-playwright-docker: + needs: build-docker + uses: ./.github/workflows/tests-e2e-playwright.yml + secrets: inherit + # with: + # debug: ${{ inputs.debug || false }} + # E2E AppImage build-appimage: uses: ./.github/workflows/pipeline-build-linux.yml diff --git a/tests/e2e/docker.web.docker-compose.yml b/tests/e2e/docker.web.docker-compose.yml index f1c417376a..2f1188bdb8 100644 --- a/tests/e2e/docker.web.docker-compose.yml +++ b/tests/e2e/docker.web.docker-compose.yml @@ -53,6 +53,8 @@ services: - rihomedir:/data - tmp:/tmp - ./test-data:/test-data + ports: + - 5540:5540 volumes: tmp: diff --git a/tests/playwright/.github/workflows/playwright.yml b/tests/playwright/.github/workflows/playwright.yml deleted file mode 100644 index a94b6417ac..0000000000 --- a/tests/playwright/.github/workflows/playwright.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Playwright Tests -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: npm install -g yarn && yarn - - name: Install Playwright Browsers - run: yarn playwright install --with-deps - - name: Run Playwright tests - run: yarn playwright test - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 0d76dc169c..5c98a08150 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -10,22 +10,19 @@ "allure-commandline": "^2.33.0", "allure-js-commons": "^3.2.0", "allure-playwright": "^3.2.0", - "cross-env": "^7.0.3" + "cross-env": "^7.0.3", + "playwright": "^1.52.0" }, "scripts": { "removeReportDirs": "rm -rf allure-results playwright-report test-results", "allTests": "playwright test", "generateReports": "allure generate --clean", - "test:chromium:docker": "cross-env envPath=env/.docker.env yarn playwright test --project=localChromium", "test:chromium:docker:debug": "yarn test:chromium:docker --debug", - "test:chromium:local-web": "cross-env envPath=env/.local-web.env yarn playwright test --project=localChromium", "test:chromium:local-web:debug": "yarn test:chromium:local-web --debug", - "test:electron": "cross-env envPath=env/.desktop.env yarn playwright test --project=localElectron", "test:electron:debug": "yarn test:electron --debug", - "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index 81efc7dc1d..cf9ff64512 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -727,6 +727,11 @@ playwright-core@1.51.0: resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.0.tgz#bb23ea6bb6298242d088ae5e966ffcf8dc9827e8" integrity sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg== +playwright-core@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.52.0.tgz#238f1f0c3edd4ebba0434ce3f4401900319a3dca" + integrity sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg== + playwright@1.51.0: version "1.51.0" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.51.0.tgz#9ba154497ba62bc6dc199c58ee19295eb35a4707" @@ -736,6 +741,15 @@ playwright@1.51.0: optionalDependencies: fsevents "2.3.2" +playwright@^1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.52.0.tgz#26cb9a63346651e1c54c8805acfd85683173d4bd" + integrity sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw== + dependencies: + playwright-core "1.52.0" + optionalDependencies: + fsevents "2.3.2" + prebuild-install@^7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" From 57b218609535eb4be7528b7662aefc81e4925b49 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Thu, 5 Jun 2025 11:46:22 +0300 Subject: [PATCH 078/128] update e2e workflow --- .github/workflows/tests-e2e-playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 9a7c4009a3..536712d7c9 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -59,7 +59,7 @@ jobs: TEST_BIG_DB_DUMP=$TEST_BIG_DB_DUMP \ docker compose -p e2e-rte \ -f tests/e2e/rte.docker-compose.yml \ - up --abort-on-container-exit --force-recreate + up --detach --force-recreate - name: Set up RI docker image run: | @@ -68,7 +68,7 @@ jobs: RI_SERVER_TLS_KEY="$RI_SERVER_TLS_KEY" \ docker compose -p e2e-ri-docker \ -f tests/e2e/docker.web.docker-compose.yml \ - up --abort-on-container-exit --force-recreate + up --detach --force-recreate - name: Run Playwright tests timeout-minutes: 80 From 885a87e993c9a4b07b646ea9596e4752599a711b Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Thu, 5 Jun 2025 11:50:19 +0300 Subject: [PATCH 079/128] update workflow --- .github/workflows/tests-e2e-playwright.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 536712d7c9..301faff4a9 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -83,3 +83,17 @@ jobs: name: playwright-report path: playwright-report/ retention-days: 30 + + - name: Clean up redis test environments + if: always() + run: | + docker compose -p e2e-rte \ + -f tests/e2e/rte.docker-compose.yml \ + down --volumes --remove-orphans + + - name: Clean up RI docker image + if: always() + run: | + docker compose -p e2e-ri-docker \ + -f tests/e2e/docker.web.docker-compose.yml \ + down --volumes --remove-orphans From 1d3679f183d298bdb788934c63f4c5dbc947eef6 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Thu, 5 Jun 2025 13:12:59 +0300 Subject: [PATCH 080/128] update playwright --- .github/workflows/tests-e2e-playwright.yml | 6 ++--- tests/playwright/package.json | 5 ++--- tests/playwright/yarn.lock | 26 +++++----------------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 301faff4a9..d976e9ad3a 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -40,9 +40,9 @@ jobs: with: dir-path: './tests/playwright' - # - name: Install Playwright Browsers - # working-directory: ./tests/playwright - # run: yarn playwright install --with-deps + - name: Install Playwright Browsers + working-directory: ./tests/playwright + run: yarn playwright install --with-deps - name: Download Docker Artifacts uses: actions/download-artifact@v4 diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 5c98a08150..931afba776 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -5,13 +5,12 @@ "license": "MIT", "devDependencies": { "@faker-js/faker": "^9.6.0", - "@playwright/test": "^1.51.0", + "@playwright/test": "^1.52.0", "@types/node": "^22.15.29", "allure-commandline": "^2.33.0", "allure-js-commons": "^3.2.0", "allure-playwright": "^3.2.0", - "cross-env": "^7.0.3", - "playwright": "^1.52.0" + "cross-env": "^7.0.3" }, "scripts": { "removeReportDirs": "rm -rf allure-results playwright-report test-results", diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index cf9ff64512..067758e923 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -28,12 +28,12 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@playwright/test@^1.51.0": - version "1.51.0" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.0.tgz#8d5c8400b465a0bfdbcf993e390ceecb903ea6d2" - integrity sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA== +"@playwright/test@^1.52.0": + version "1.52.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.52.0.tgz#267ec595b43a8f4fa5e444ea503689629e91a5b8" + integrity sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g== dependencies: - playwright "1.51.0" + playwright "1.52.0" "@tootallnate/once@1": version "1.1.2" @@ -722,26 +722,12 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -playwright-core@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.0.tgz#bb23ea6bb6298242d088ae5e966ffcf8dc9827e8" - integrity sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg== - playwright-core@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.52.0.tgz#238f1f0c3edd4ebba0434ce3f4401900319a3dca" integrity sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg== -playwright@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.51.0.tgz#9ba154497ba62bc6dc199c58ee19295eb35a4707" - integrity sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA== - dependencies: - playwright-core "1.51.0" - optionalDependencies: - fsevents "2.3.2" - -playwright@^1.52.0: +playwright@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.52.0.tgz#26cb9a63346651e1c54c8805acfd85683173d4bd" integrity sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw== From 984ef6afcaba946f255c72fed5dcc0a32cf6a1b2 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Thu, 5 Jun 2025 17:59:59 +0300 Subject: [PATCH 081/128] update playwright tests and fixtures --- .github/workflows/tests.yml | 4 + tests/e2e/.env | 84 ----------- tests/playwright/env/.desktop.env | 3 +- tests/playwright/fixtures/open-ri.ts | 87 ----------- tests/playwright/fixtures/simple-slectron.ts | 139 ------------------ tests/playwright/fixtures/test.ts | 75 ++++++++++ tests/playwright/helpers/conf.ts | 4 +- tests/playwright/playwright.config.ts | 7 +- ...ample.electron.spec.ts => example.spec.ts} | 27 ++-- tests/playwright/tests/example.web.spec.ts | 37 ----- 10 files changed, 100 insertions(+), 367 deletions(-) delete mode 100644 tests/playwright/fixtures/open-ri.ts delete mode 100644 tests/playwright/fixtures/simple-slectron.ts create mode 100644 tests/playwright/fixtures/test.ts rename tests/playwright/tests/{example.electron.spec.ts => example.spec.ts} (66%) delete mode 100644 tests/playwright/tests/example.web.spec.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a727764ab2..f1ba6a5908 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,6 +72,7 @@ jobs: backend: ${{ steps.filter.outputs.backend }} desktop: ${{ steps.filter.outputs.desktop }} e2e: ${{ steps.filter.outputs.e2e }} + e2ePlaywright: ${{ steps.filter.outputs.e2ePlaywright }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3.0.2 @@ -87,6 +88,8 @@ jobs: - 'redisinsight/desktop/**' e2e: - 'tests/e2e/**' + e2ePlaywright: + - 'tests/playwright/**' frontend-tests: needs: changes @@ -155,6 +158,7 @@ jobs: for_e2e_tests: true e2e-docker-tests: + if: inputs.group_tests == 'never' needs: build-docker uses: ./.github/workflows/tests-e2e-docker.yml secrets: inherit diff --git a/tests/e2e/.env b/tests/e2e/.env index 3a47cf8321..21c248e7a3 100644 --- a/tests/e2e/.env +++ b/tests/e2e/.env @@ -7,87 +7,3 @@ RI_NOTIFICATION_SYNC_INTERVAL=30000 RI_FEATURES_CONFIG_URL=http://static-server:5551/remote/features-config.json RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 TEST_BIG_DB_DUMP=https://s3.amazonaws.com/redisinsight.test/public/rte/dump/big/dump.tar.gz -RI_ENCRYPTION_KEY=ba843af5c43bb6c90538534daf2dd0c2 -RI_SERVER_TLS_CERT='-----BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIUNPjaFDbh/Y0nCQzp5KYuvYc43zEwDQYJKoZIhvcNAQEL -BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAxMTAxNzIzNDVaFw0zNDAx -MDcxNzIzNDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCybIVgtjaaRgvJi/mZ91Qi42mXE0ueASXNn5qbMJ85 -dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kWE3lwCn5A1RPOBfDA8voSMPw4HSa9 -Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5fbJXM5uqf1fe1NE+HsUBKOdPBFNl4 -jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rFh3ggXZpDzq79ykLCK6MkaQcXYwls -qghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299v2vyRNm59u51c4g5dKxKPBPQw+06 -bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJyj8GP9Ej7XwQ9XQDiZc7/qzTamvE -yz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfIpHi/AJ//1yfPBqFLWY5B5f5fWQHv -e/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGDr9e25h3N3d6YRYKAMLiRxE2sFfni -79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQhA4gqNYIQ6KHvGDZCn6aNi8EgGYbp -PTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxMAUBesPIRj7kz5B6x3DfsBq/ZOzJW -Um35FPOdqGosqws31WatOTQIoXDV9lf86ew8e3W6O4O42szmcX2bVekNmGQTV9fX -OwIDAQABo1MwUTAdBgNVHQ4EFgQUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwHwYDVR0j -BBgwFoAUH9HsPB+Z8CBN3MCBBQ8EZvHx5JgwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAgEAk/YnVSsxvsNw43acfXHVNMffL3Uwy2v7UMdc0+nmuFtG -dj4RNVyW42jZXaeEzckEBr/HQLY0X4XA3SDRF6OXgdgyAFKSMChU0b7ldXTZicYO -cjOHztNBQ8NHXCSHJjzjJixgIKj88qAHzFNg89lA9RYjhiGhmR6iKe+FGpf8XyG9 -gF1mnqyWFGevsEgmNED/KgM1EZtix84hhuUvo1FoyjM4gkR6IQHkVwfDssRgZUaI -1TFURNLB3YSbjJ+vygBjYZbW29xLQ8MIZnT9uDmUoM1RKg33Fb3clsuF4U6hMOLo -TPFcBwrejY/3eMOZd1JqFN+JS4cRWhHVAk+Eurora76hBowMsYEsOBcrqDTZsFEU -x8qZbQLTCKy4HpvBAAH/P3gOeLEVYwCkTAA1w6/BLodu8dCJnKxylEuHoLSSm3xQ -pxRhb2dZHdd1gUikZ3rbFnUj9YDWz5eWn19FHkYlLXoxrQEkz20Mim7gJvbzLJbG -nPeVnoaFxr//sGJu+Ilq+heXeXqXHorOtfYbSlWJXUix67n+JvtVTHsdQ1PPMgGl -5hAD/oVNgdmWth/k6RDsKkrklkK5rEVkyeescpyhLRJOa/4l2xAQziBfQGijnTTb -NdFUmSSGFeptHPvjNLULF8kfAGEdmrGIttExvCwOci9F/k9OdmlTK2Z8UlGHwIA= ------END CERTIFICATE-----' -RI_SERVER_TLS_KEY='-----BEGIN PRIVATE KEY----- -MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCybIVgtjaaRgvJ -i/mZ91Qi42mXE0ueASXNn5qbMJ85dwrzC/SVLPTUCKXe1I/TfSj4bEsyWBbfJ6kW -E3lwCn5A1RPOBfDA8voSMPw4HSa9Tdpyn5FyCSUom7iH6CKH4n8CL2BvN8swsh5f -bJXM5uqf1fe1NE+HsUBKOdPBFNl4jpP7aL0nwmhKCUM9BakMkcq62vTSBzj/i5rF -h3ggXZpDzq79ykLCK6MkaQcXYwlsqghHDQqwNFzk6fW6U3cVgoQ/v+SzgwyLX299 -v2vyRNm59u51c4g5dKxKPBPQw+06bFxrWayQmYYve2aVmTTRYfLZJih+XFOsezOJ -yj8GP9Ej7XwQ9XQDiZc7/qzTamvEyz9gE6whBWXo89ApLd3YqjtEmd0v0LE5WWfI -pHi/AJ//1yfPBqFLWY5B5f5fWQHve/kBBZxt8OzjVDQcmQJuWKLQ5qyQQPMsakGD -r9e25h3N3d6YRYKAMLiRxE2sFfni79fayDUi+ePPMRsljcr8bI7Ze1Wn4R+vyVQh -A4gqNYIQ6KHvGDZCn6aNi8EgGYbpPTVRm/nea5FfF1wrwu59A3mDeP7SBwQnACxM -AUBesPIRj7kz5B6x3DfsBq/ZOzJWUm35FPOdqGosqws31WatOTQIoXDV9lf86ew8 -e3W6O4O42szmcX2bVekNmGQTV9fXOwIDAQABAoICAQCJAQFtoJzO22hjq4LOsfa+ -D2dd5SgUPIddm+dosO4ifwE+XXjCL1ITmkxbjVafK6URFH6tOqzdT6PrWqrN2JDX -kYXylecnEavp2glhwSilBanuiA5zxQfuZZxNZ3dUZhvmfqCK5gm066Cc31ErlEim -0PKzBmbnJ7jZBgxOX4cZpkmFLAjLBeF0sCYcLkN/bleAIW8J8xfWSclfUcVw/M7e -sE74e53FYSKVa7xRPe/Xq7xNantBkAOglvHj0AFJ1/1awiuHl+JDBtYidaEa17lj -rXOvZjY9ABTnr7f7fuajDN/uYl46blh2D0hXKNxAxvhlu4IufRCXCccqT80TLF+W -8Wlj6qysCESGTCn1xBnyriqv4ZtDgWS7gHEJqghdJr5x4q5BXpA2pWcyO5JSWrqg -kVE9rnV5j8c4hP86DNrtT4FdX9eAxMZvYmPOjF9TPOZGU7zDEpf00c7Gg9TZOlq7 -mUy7K0ybsgXQXTRDDv5prBRJ6A4mXBKZF2mQeUQPco90Wct3mAqTWPiUaCQ8+IPx -rR1BLGyTNwld8m9PxGyD8kQ3hkzMyUCh5ykqIJcRGUP4JS5xq29YJvABOxikJpRU -7QQ42k97mV7SVedPOSZFAoEhtgIbKh31OVNVulHNHac57AXwEvlLfPfmIrwSTFy6 -qElEkcFeqbxy8ZNiVJ7gMQKCAQEA3fT79JsCKtnjQSKEDdegWboW2m1jOk9f43jO -FiaaJia+i6ZuNzskOOK8F8J+C+nbWeKVRmsyCqsd+N+IB0X25PpcD1ZoXhXYBFp9 -QxANmY0LMv6d+iAv5wQ4ymQsLvjW0qdEgAKBKB7y/wnfITfbKmgm7zZzS4Qg/r6x -iPwY/1uxedJ/xC9ME7Vlo4p+pqq1pvS9ge8x51kuEB+GcmEGYBaCiVnazAZJSjcP -51EYdTQMt6nlPQu16YznlpazW8Nj5mrmxcMNbOJwphgyrOFTm3jWeaq6E3DR6Ion -FkI4hvC4f4sk5L6zVwkD5Vz0UhboZ6+VFwH7cXFN1Fh11pWkIwKCAQEAzco8VOZC -Qwwp81NEfXfuIfDRVBbTvv1W342HOSNdguWcqYDMnTE3herW4voOM4cN8tHiiybV -Ds5C3sVc+nG2mOdusP4PpQu8ZrC7eMYjFtSZjPDDBohkPuQFl1TXNvljiseM3o4m -DxQYnMxvR73zwYT6HVCY47+KqG4ll1xbqGtw2OxJFdTYgy5zqbsS6NVHqmp8Dytr -Jp5yX1sKY+uHnewJBIgzaVkQA2OFfQny5GQh1Gu3UZQGFYcxH15jeIFFQuf4FEyL -TiDGjnHOhz922Z7fGsom+vM+PrQqWhqlSAGURfnWE/9+7SQ/16nwgRPa+t7IR8m5 -qx6x/qr5svbGCQKCAQBCOyxD3U1URBhQA2CsUL+Ehsby/tkxOSY/1niIm5Q79iu9 -uDgDOx6f6uh9nofGPk46ECbDEJGqJU2YSftpUDNQOHToDryt9o6zHd1q+YtVWoUQ -/nFdheVFZjkcC7AzhAV2bns+R4EK29Fe0S9H1kcL7HBRyUm2KwM9gOGxIqjC6jWX -SHzfqc1lxCdEGbuZOC9kVnuGHj4h7raUERQpZVJlsdHZ8nobj3SnNK8GM2i88H8q -/wNsp+XsfyNMCEQVCcTxqMycDowfBaLfrTDR7ZrpNbGqNIu56Vx5q1/mgHQlsAcd -6ANmTpFtUz9aXdZ5+GP5LKesaecB/vFef9cJ5TVJAoIBAQCLWXiFHO6drCz0AdyN -AZsVDJcS0+pKmA4tjw6UGGiH7Keq9/aWMu+gFSexxxW6uqctWAaJp5/6SJ1rcEXH -qRy2aXDBFSjO4CWRe/dWjwTPvuLDV30YfV80Xv+SO/cH2NQY84tmYOdQqpEIDD8G -W5Al6L/c/eipv9hKuVtnJTlD0I03tri+ucRrABd+RZlGZLYEpdqgAwypt/1GqMVe -Z+0LePuaQAhgO9jmEowf4Y7CLTPjPZWC/jMofMU8spO01ARsi9unEzX5j6QkbNgn -KUh3kGcPIvhGUlRB7YoIPabSHY+j2sul+wqd1kAM75xWK3XLDvSb9/Nr9nsdMfWn -xAbRAoIBAQDFAiuvAuyUepnmJThP6hO9Xg90e6gD8c6zHs6ezIm8CRaOmb9FAM0l -nVz7oLsmgEuowrx1npTCzDr/RPBy94SpFbZMLzOL3cZVLd5AEDxsbt1cBUGy/Gda -dkV0J7YXeo0FVBKleM3GbpPjKgtmOMGfNmVKa3StkxvrRx4Nvro5DdhjM7CwBy7J -oNPYhCXXhvTzYvSFWQXmCJxDCKGeK7jKid2T9nlA/lCzR52m3LrBnAczTIGTPJDp -b1eyrSDuiaPs+gFn6Y94QmfNztstONlk0qRQpS+oTlmThNULJBfCJADIEXt9fVmR -flQdMlQ3lR1FtqYSypwbggaJ71j3gHU4 ------END PRIVATE KEY-----' diff --git a/tests/playwright/env/.desktop.env b/tests/playwright/env/.desktop.env index 001833aa31..84513a228b 100644 --- a/tests/playwright/env/.desktop.env +++ b/tests/playwright/env/.desktop.env @@ -1,5 +1,6 @@ -COMMON_URL=../../release/mac-arm64/Redis Insight.app/Contents/MacOS/Redis Insight +COMMON_URL= API_URL=http://localhost:5530/api +ELECTRON_EXECUTABLE_PATH=../../release/mac-arm64/Redis Insight.app/Contents/MacOS/Redis Insight RI_APP_FOLDER_NAME=.redis-insight-stage diff --git a/tests/playwright/fixtures/open-ri.ts b/tests/playwright/fixtures/open-ri.ts deleted file mode 100644 index 55d4c651bd..0000000000 --- a/tests/playwright/fixtures/open-ri.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { test as base, Page } from '@playwright/test' -import { BasePage } from '../pageObjects/base-page' -import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialog' -import { DatabaseAPIRequests } from '../helpers/api/api-databases' -import { ossStandaloneConfig } from '../helpers/conf' -import { MyRedisDatabasePage } from '../pageObjects/my-redis-databases-page' -import log from 'node-color-log' - -// Define shared worker object -type WorkerSharedState = { - apiUrl: string; - dbConfig: typeof ossStandaloneConfig; - baseUrl: string; -} - -// Define test fixture types -type RedisInsight = { - basePage: Page; - dialogUserAgreement: UserAgreementDialog; - workerState: WorkerSharedState; // Worker-scoped object passed to tests -} - -// Extend Playwright test -export const test = base.extend< - RedisInsight, - { forEachWorker: void; workerState: WorkerSharedState } >({ - - // ✅ Worker-scoped shared object - workerState: [async ({}, use, testInfo) => { - log.info(`🚀 Setting up worker state for worker ${testInfo.workerIndex}`) - - // Initialize worker-scoped data - const workerState: WorkerSharedState = { - apiUrl: testInfo.project.use.apiUrl, - dbConfig: ossStandaloneConfig, - baseUrl: testInfo.project.use.baseURL - } - log.info(`🏠 Base URL: ${workerState.baseUrl}`) - log.info(`🌐 API URL: ${workerState.apiUrl}`) - log.info(`🗄️ Database Config: ${JSON.stringify(workerState.dbConfig)}`) - - await use(workerState) - - }, { scope: 'worker' }], // Worker-scoped fixtures, runs once per worker - - // ✅ Worker-scoped setup/teardown - forEachWorker: [async ({ workerState }, use) => { - const ti = base.info().workerIndex - log.info(`BEFORE Starting test worker ${ti}`) - - // Set up the database before tests - const dbApi = new DatabaseAPIRequests(workerState.apiUrl) - await dbApi.addNewStandaloneDatabaseApi(workerState.dbConfig) - - await use() // Run the tests - // Something failing here doesn't affect test execution result - log.info(`Stopping test worker ${ti}`) - - // Cleanup after all tests in this worker - // Delete db - await dbApi.deleteStandaloneDatabaseApi(workerState.dbConfig) - - }, { scope: 'worker', auto: true }], - - // ✅ Test-scoped `basePage` using worker state - basePage: async ({ page, workerState }, use) => { - log.info('Fixture setup: Initializing Base Page') - - // Navigate to home page - const basePage = new BasePage(page) - await basePage.navigateToHomeUrl(workerState.baseUrl) - - const userAgreementDialog = new UserAgreementDialog(page) - await userAgreementDialog.acceptLicenseTerms() - - - // Enter DB - const myDbPage = new MyRedisDatabasePage(page) - await myDbPage.clickOnDBByName(workerState.dbConfig.databaseName) - - await use(page) - }, - - -}) - -export { expect } from '@playwright/test' diff --git a/tests/playwright/fixtures/simple-slectron.ts b/tests/playwright/fixtures/simple-slectron.ts deleted file mode 100644 index fbf960cb7f..0000000000 --- a/tests/playwright/fixtures/simple-slectron.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* eslint-disable no-empty-pattern */ -/* eslint-disable no-param-reassign */ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable no-await-in-loop */ -import { test as base, ElectronApplication, Page } from '@playwright/test' -import { _electron as electron } from 'playwright' -import log from 'node-color-log' -import { ossStandaloneConfig } from '../helpers/conf' - -// Define shared state for worker scope -type WorkerSharedState = { - apiUrl: string - dbConfig: typeof ossStandaloneConfig - baseUrl: string - electronApp: ElectronApplication -} - -type ElectronFixture = { - electronApp: ElectronApplication - electronPage: Page -} - -async function launchElectronApp( - baseUrl: string, -): Promise { - const electronApp = await electron.launch({ - executablePath: baseUrl, - args: ['index.html'], - timeout: 60000, - }) - // Capture Electron logs - electronApp.on('console', (msg) => { - log.info(`Electron Log: ${msg.type()} - ${msg.text()}`) - }) - - return electronApp -} - -async function waitForWindowWithTitle( - electronApp: ElectronApplication, - - maxWaitTime = 5000, - interval = 200, -): Promise { - const startTime = Date.now() - - while (Date.now() - startTime < maxWaitTime) { - const windows = await electronApp.windows() - for (const window of windows) { - try { - const title = await window.title() - - if (title) { - log.info(`✅ Found window with title: "${title}"`) - return window - } - } catch (e) { - log.info('❌ Window title not found') - } - } - await new Promise((resolve) => setTimeout(resolve, interval)) - } - - log.error(`❌ Window not found within ${maxWaitTime / 1000}s!`) - throw Error('❌ Window not found ') -} - -async function waitForWindows( - electronApp: ElectronApplication, - maxWaitTime = 60000, - interval = 2000, -) { - let windows: Page[] = [] - let elapsedTime = 0 - while (windows.length === 0 && elapsedTime < maxWaitTime) { - await new Promise((resolve) => setTimeout(resolve, interval)) - windows = await electronApp.windows() - elapsedTime += interval - log.info(`🔍 Checking for windows... (${elapsedTime / 1000}s elapsed)`) - } - return windows -} - -export const test = base.extend< - ElectronFixture, - { workerState: WorkerSharedState } ->({ - workerState: [ - async ({}, use, testInfo) => { - log.info( - `🚀 Setting up worker state for worker ${testInfo.workerIndex}`, - ) - const workerState: WorkerSharedState = { - // @ts-expect-error - apiUrl: testInfo.project.use.apiUrl, - dbConfig: ossStandaloneConfig, - baseUrl: testInfo.project.use.baseURL || '', - electronApp: null as any, - } - - await use(workerState) - }, - { scope: 'worker' }, - ], - - electronApp: async ({ workerState }, use) => { - log.info('🚀 Starting RedisInsight...') - - const electronApp = await launchElectronApp(workerState.baseUrl) - workerState.electronApp = electronApp - log.info('⏳ Waiting for window...') - const windows = await waitForWindows(electronApp) - - if (windows.length === 0) { - log.error('❌ No windows detected after 60s! Exiting.') - await electronApp.close() - return - } - - log.info(`✅ Found ${windows.length} window(s)!`) - await use(electronApp) - }, - - electronPage: async ({ electronApp }, use) => { - const window = await waitForWindowWithTitle(electronApp) - - if (!window) { - log.error('❌ No matching window detected! Stopping test.') - await electronApp.close() - return - } - - await window.waitForLoadState('domcontentloaded') - log.info(`🖥️ Window Title: ${await window.title()}`) - await use(window) - }, -}) - -export { expect } from '@playwright/test' diff --git a/tests/playwright/fixtures/test.ts b/tests/playwright/fixtures/test.ts new file mode 100644 index 0000000000..16c93c20a1 --- /dev/null +++ b/tests/playwright/fixtures/test.ts @@ -0,0 +1,75 @@ +/* eslint-disable no-empty-pattern */ +import { test as base, expect } from '@playwright/test' +import type { + Fixtures, + PlaywrightTestArgs, + PlaywrightTestOptions, + PlaywrightWorkerArgs, + PlaywrightWorkerOptions, +} from '@playwright/test' +import { + BrowserContext, + ElectronApplication, + Page, + _electron as electron, +} from 'playwright' +import log from 'node-color-log' + +const isElectron = process.env.ELECTRON_EXECUTABLE_PATH !== undefined + +base.beforeEach(async ({ page }) => { + if (!isElectron) { + await page.goto('/') + } + + await page.waitForSelector('[aria-label="Main navigation"]') +}) + +type ElectronTestFixtures = { + electronApp: ElectronApplication + page: Page + context: BrowserContext +} + +export const electronFixtures: Fixtures< + ElectronTestFixtures, + {}, + PlaywrightTestArgs & PlaywrightTestOptions, + PlaywrightWorkerArgs & PlaywrightWorkerOptions +> = { + electronApp: async ({}, use) => { + const electronApp = await electron.launch({ + executablePath: process.env.ELECTRON_EXECUTABLE_PATH, + args: ['index.html'], + timeout: 60000, + }) + electronApp.on('console', (msg) => { + log.info(`Electron Log: ${msg.type()} - ${msg.text()}`) + }) + + // Wait for window startup + await new Promise((resolve) => setTimeout(resolve, 1000)) + + await use(electronApp) + + log.info('Closing Electron app...') + await electronApp.close() + }, + page: async ({ electronApp }, use) => { + const page = await electronApp.firstWindow() + + await use(page) + }, + context: async ({ electronApp }, use) => { + const context = electronApp.context() + + await use(context) + }, +} + +const electronTest = base.extend(electronFixtures) +const browserTest = base + +const test = isElectron ? electronTest : browserTest + +export { test, expect, isElectron } diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts index e4c69740a5..230f2cec9d 100644 --- a/tests/playwright/helpers/conf.ts +++ b/tests/playwright/helpers/conf.ts @@ -19,8 +19,8 @@ export const fileDownloadPath = joinPath(os.homedir(), 'Downloads') const uniqueId = faker.string.alphanumeric({ length: 10 }) export const ossStandaloneConfig = { - host: process.env.OSS_STANDALONE_HOST, - port: process.env.OSS_STANDALONE_PORT, + host: process.env.OSS_STANDALONE_HOST!, + port: process.env.OSS_STANDALONE_PORT!, databaseName: `${process.env.OSS_STANDALONE_DATABASE_NAME || 'test_standalone'}-${uniqueId}`, databaseUsername: process.env.OSS_STANDALONE_USERNAME, databasePassword: process.env.OSS_STANDALONE_PASSWORD, diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 9c30cc5331..719718b4db 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -94,7 +94,7 @@ export default defineConfig({ projects: [ { name: 'localChromium', - testMatch: ['**.web.spec.ts'], + testMatch: ['**.spec.ts'], use: { ...devices['Desktop Chrome'], baseURL: process.env.COMMON_URL, @@ -115,10 +115,9 @@ export default defineConfig({ }, { name: 'localElectron', - testMatch: ['**.electron.spec.ts'], + testMatch: ['**.spec.ts'], use: { - baseURL: - process.env.COMMON_URL, + // baseURL: process.env.COMMON_URL, apiUrl: process.env.API_URL, headless: false, }, diff --git a/tests/playwright/tests/example.electron.spec.ts b/tests/playwright/tests/example.spec.ts similarity index 66% rename from tests/playwright/tests/example.electron.spec.ts rename to tests/playwright/tests/example.spec.ts index f41f235f57..9c6c1bef3b 100644 --- a/tests/playwright/tests/example.electron.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -1,40 +1,41 @@ -import { test, expect } from '../fixtures/simple-slectron' +/* eslint-disable no-empty-pattern */ import { Common } from '../helpers/common' import { BrowserPage } from '../pageObjects/browser-page' import { DatabaseHelper } from '../helpers/database' import { APIKeyRequests } from '../helpers/api/api-keys' import { DatabaseAPIRequests } from '../helpers/api/api-databases' +import { test, expect } from '../fixtures/test' +import { ossStandaloneConfig } from '../helpers/conf' let keyName: string let browserPage: BrowserPage let databaseHelper: DatabaseHelper -test.beforeEach(async ({ electronPage, workerState }) => { +test.beforeEach(async ({ page }, testInfo) => { // await electronPage.getByText('Add Redis').click() - browserPage = new BrowserPage(electronPage) - databaseHelper = new DatabaseHelper(electronPage, workerState.apiUrl) + browserPage = new BrowserPage(page) + databaseHelper = new DatabaseHelper(page, testInfo.project.use.apiUrl) await databaseHelper.acceptLicenseTermsAndAddDatabaseApi( - workerState.dbConfig, - electronPage, - workerState.apiUrl, + ossStandaloneConfig, + page, + testInfo.project.use.apiUrl, ) keyName = Common.generateAlphanumeric(5) }) -test.afterEach(async ({ workerState }) => { - const apiKeyClient = new APIKeyRequests(workerState.apiUrl) - const dbApi = new DatabaseAPIRequests(workerState.apiUrl) +test.afterEach(async ({}, testInfo) => { + const apiKeyClient = new APIKeyRequests(testInfo.project.use.apiUrl) + const dbApi = new DatabaseAPIRequests(testInfo.project.use.apiUrl) await apiKeyClient.deleteKeyByNameApi( keyName, - workerState.dbConfig.databaseName, + ossStandaloneConfig.databaseName, await browserPage.getWindowId(), ) await dbApi.deleteStandaloneDatabaseApi( - workerState.dbConfig, + ossStandaloneConfig, await browserPage.getWindowId(), ) - await workerState.electronApp.close() }) test('basic test', async () => { diff --git a/tests/playwright/tests/example.web.spec.ts b/tests/playwright/tests/example.web.spec.ts deleted file mode 100644 index b1300c3076..0000000000 --- a/tests/playwright/tests/example.web.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {test, expect} from '../fixtures/open-ri' -import {Common} from '../helpers/common' -import {BrowserPage} from '../pageObjects/browser-page' -import {APIKeyRequests} from '../helpers/api/api-keys' - -let keyName: string -let browserPage: BrowserPage - -test.beforeEach(async ({basePage}) => { - - console.log('WE ARE IN THE BEFORE STEP') - keyName = Common.generateAlphanumeric(10) - browserPage = new BrowserPage(basePage) - -}) - -test.afterEach(async ({basePage, workerState }) => { - console.log('WE ARE IN THE AFTER STEP') - const apiKeyClient = new APIKeyRequests(workerState.apiUrl) - await apiKeyClient.deleteKeyByNameApi(keyName, workerState.dbConfig.databaseName) - -}) - -test('basic test', async ({basePage}) => { - - console.log('WE ARE IN TEST') - await browserPage.addHashKey(keyName) - - // checks that the notification is displayed (should be in a different test) - await expect(await basePage.getByText('Key has been added')).toBeVisible() - - // Check that new key is displayed in the list - await browserPage.searchByKeyName(keyName) - const isKeyIsDisplayedInTheList = await browserPage.isKeyIsDisplayedInTheList(keyName) - await expect(isKeyIsDisplayedInTheList).toBe(true) - -}) From e1b3aa8c356d563e807b9425b70f0a79e31fbf4a Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 09:57:02 +0300 Subject: [PATCH 082/128] use headless mode for testing --- tests/playwright/playwright.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 719718b4db..51ab2a10fb 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -99,7 +99,7 @@ export default defineConfig({ ...devices['Desktop Chrome'], baseURL: process.env.COMMON_URL, apiUrl: process.env.API_URL, - headless: false, + headless: true, deviceScaleFactor: undefined, viewport: null, launchOptions: { @@ -119,7 +119,7 @@ export default defineConfig({ use: { // baseURL: process.env.COMMON_URL, apiUrl: process.env.API_URL, - headless: false, + headless: true, }, }, ], From 286edcf605fd5bc7879a460a2a6f37b26dac6bde Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 11:16:06 +0300 Subject: [PATCH 083/128] update workflows --- .github/workflows/tests-e2e-playwright.yml | 10 +++++++++- .github/workflows/tests.yml | 1 + tests/playwright/fixtures/test.ts | 4 +++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index d976e9ad3a..b2d5d249c2 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -33,7 +33,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v4 - - uses: ./.github/actions/install-all-build-libs + # - uses: ./.github/actions/install-all-build-libs - name: Install dependencies for Playwright tests uses: ./.github/actions/install-deps @@ -70,6 +70,14 @@ jobs: -f tests/e2e/docker.web.docker-compose.yml \ up --detach --force-recreate + - name: Debug docker containers + run: | + docker ps + + - name: Test connectivity to Redis Insight + run: | + curl -v https://localhost:5540/ + - name: Run Playwright tests timeout-minutes: 80 working-directory: ./tests/playwright diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f1ba6a5908..e0efa8f155 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -174,6 +174,7 @@ jobs: # E2E AppImage build-appimage: + if: inputs.group_tests == 'never' uses: ./.github/workflows/pipeline-build-linux.yml needs: e2e-approve secrets: inherit diff --git a/tests/playwright/fixtures/test.ts b/tests/playwright/fixtures/test.ts index 16c93c20a1..6fbe381686 100644 --- a/tests/playwright/fixtures/test.ts +++ b/tests/playwright/fixtures/test.ts @@ -22,7 +22,9 @@ base.beforeEach(async ({ page }) => { await page.goto('/') } - await page.waitForSelector('[aria-label="Main navigation"]') + await page.waitForSelector('[aria-label="Main navigation"]', { + timeout: 10000, + }) }) type ElectronTestFixtures = { From 14daebe5d80daa3aa381dec05ce24f0e53d68eea Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 11:28:42 +0300 Subject: [PATCH 084/128] update playwright config --- tests/playwright/playwright.config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 51ab2a10fb..d16067d2dd 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -84,6 +84,9 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', testIdAttribute: 'data-testid', + headless: true, + deviceScaleFactor: undefined, + viewport: { width: 1920, height: 1080 }, video: { mode: 'on', size: { width: 1920, height: 1080 }, @@ -99,9 +102,7 @@ export default defineConfig({ ...devices['Desktop Chrome'], baseURL: process.env.COMMON_URL, apiUrl: process.env.API_URL, - headless: true, - deviceScaleFactor: undefined, - viewport: null, + // headless: false, launchOptions: { args: [ '--start-maximized', @@ -119,7 +120,6 @@ export default defineConfig({ use: { // baseURL: process.env.COMMON_URL, apiUrl: process.env.API_URL, - headless: true, }, }, ], From 1e3c0d3396e44d911dfeb8f26122582b570c82b7 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 11:33:20 +0300 Subject: [PATCH 085/128] update tests workflow --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e0efa8f155..ecffdd398b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,7 @@ on: group_tests: description: Run group of tests type: string - default: 'without_e2e' + default: 'only_e2e' short_rte_list: description: Use short rte list type: boolean @@ -141,7 +141,7 @@ jobs: e2e-approve: runs-on: ubuntu-latest needs: changes - # if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') + if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') timeout-minutes: 60 environment: ${{ startsWith(github.ref_name, 'e2e/') && 'e2e-approve' || 'staging' }} name: Approve E2E tests From 9e8b43c1d68faaeb54dff07b5e0e8f737e168dee Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 11:37:55 +0300 Subject: [PATCH 086/128] Revert "update tests workflow" This reverts commit 1e3c0d3396e44d911dfeb8f26122582b570c82b7. --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ecffdd398b..e0efa8f155 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,7 +45,7 @@ on: group_tests: description: Run group of tests type: string - default: 'only_e2e' + default: 'without_e2e' short_rte_list: description: Use short rte list type: boolean @@ -141,7 +141,7 @@ jobs: e2e-approve: runs-on: ubuntu-latest needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') + # if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') timeout-minutes: 60 environment: ${{ startsWith(github.ref_name, 'e2e/') && 'e2e-approve' || 'staging' }} name: Approve E2E tests From 5a3905da7feb7d4326a0336948857920842b04fb Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 12:04:26 +0300 Subject: [PATCH 087/128] add axios for playwright --- tests/playwright/package.json | 1 + tests/playwright/yarn.lock | 161 ++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 931afba776..216e6a3c72 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -30,6 +30,7 @@ "test:autogen": "playwright codegen" }, "dependencies": { + "axios": "^1.9.0", "dotenv": "^16.4.7", "dotenv-cli": "^8.0.0", "fs-extra": "^11.3.0", diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index 067758e923..5965c8252c 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -111,6 +111,20 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.9.0.tgz#25534e3b72b54540077d33046f77e3b8d7081901" + integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -177,6 +191,14 @@ cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + charenc@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" @@ -202,6 +224,13 @@ color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -252,6 +281,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -282,6 +316,15 @@ dotenv@^16.3.0, dotenv@^16.4.7: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -311,6 +354,33 @@ err-code@^2.0.2: resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -321,6 +391,22 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae" + integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -352,6 +438,11 @@ fsevents@2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + gauge@^4.0.3: version "4.0.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" @@ -366,6 +457,30 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -383,16 +498,40 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" @@ -538,6 +677,11 @@ make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -547,6 +691,18 @@ md5@^2.3.0: crypt "0.0.2" is-buffer "~1.1.6" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -767,6 +923,11 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pump@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" From 832e0992d5bb822b70418a348adc7af8c8a12338 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 12:22:55 +0300 Subject: [PATCH 088/128] single worker for electron tests --- tests/playwright/playwright.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index d16067d2dd..37ba265066 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -117,6 +117,7 @@ export default defineConfig({ { name: 'localElectron', testMatch: ['**.spec.ts'], + workers: 1, use: { // baseURL: process.env.COMMON_URL, apiUrl: process.env.API_URL, From 839c79cb9b0cc4bb2b571b73912eab447d0d8521 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 12:41:00 +0300 Subject: [PATCH 089/128] debug pipeline --- .github/workflows/tests-e2e-playwright.yml | 15 +++++++++++++++ tests/playwright/fixtures/test.ts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index b2d5d249c2..46516cef7d 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -73,11 +73,26 @@ jobs: - name: Debug docker containers run: | docker ps + docker network inspect e2e-ri-docker + + - name: Debug Redis Insight Container + run: docker logs $(docker ps -q --filter "name=e2e-ri-docker") + + - name: Debug env variables + run: | + echo "RI_ENCRYPTION_KEY: $RI_ENCRYPTION_KEY" + echo "E2E_RI_ENCRYPTION_KEY: $E2E_RI_ENCRYPTION_KEY" + echo "RI_SERVER_TLS_CERT: $RI_SERVER_TLS_CERT" + echo "RI_SERVER_TLS_KEY: $RI_SERVER_TLS_KEY" - name: Test connectivity to Redis Insight run: | curl -v https://localhost:5540/ + - name: Test connectivity to Redis Insight (no SSL) + run: | + curl -v -k https://localhost:5540/ + - name: Run Playwright tests timeout-minutes: 80 working-directory: ./tests/playwright diff --git a/tests/playwright/fixtures/test.ts b/tests/playwright/fixtures/test.ts index 6fbe381686..82eb68c96f 100644 --- a/tests/playwright/fixtures/test.ts +++ b/tests/playwright/fixtures/test.ts @@ -19,7 +19,7 @@ const isElectron = process.env.ELECTRON_EXECUTABLE_PATH !== undefined base.beforeEach(async ({ page }) => { if (!isElectron) { - await page.goto('/') + await page.goto('/', { timeout: 5000 }) } await page.waitForSelector('[aria-label="Main navigation"]', { From 01961f8eb135fe731dbb80a9e1ec19dc5b3061b7 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 13:49:46 +0300 Subject: [PATCH 090/128] update workflow --- .github/workflows/tests-e2e-playwright.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 46516cef7d..b778b7b75d 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -73,7 +73,6 @@ jobs: - name: Debug docker containers run: | docker ps - docker network inspect e2e-ri-docker - name: Debug Redis Insight Container run: docker logs $(docker ps -q --filter "name=e2e-ri-docker") From 5d99e99ec8da4ef1b19f72c904768ec5d06c9b33 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 14:38:32 +0300 Subject: [PATCH 091/128] update workflow --- .github/workflows/tests-e2e-playwright.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index b778b7b75d..fd77694fca 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -74,9 +74,6 @@ jobs: run: | docker ps - - name: Debug Redis Insight Container - run: docker logs $(docker ps -q --filter "name=e2e-ri-docker") - - name: Debug env variables run: | echo "RI_ENCRYPTION_KEY: $RI_ENCRYPTION_KEY" From 3c60dfcf0193c252bdb032802bde81caefe29f66 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 15:07:04 +0300 Subject: [PATCH 092/128] fix multi test runs --- tests/playwright/fixtures/test.ts | 71 +++++++++++++------------ tests/playwright/helpers/conf.ts | 2 + tests/playwright/package.json | 6 +-- tests/playwright/playwright.config.ts | 15 ++---- tests/playwright/tests/example.spec.ts | 14 ++--- tests/playwright/tests/example2.spec.ts | 11 ++++ 6 files changed, 62 insertions(+), 57 deletions(-) create mode 100644 tests/playwright/tests/example2.spec.ts diff --git a/tests/playwright/fixtures/test.ts b/tests/playwright/fixtures/test.ts index 82eb68c96f..5321fe5ccc 100644 --- a/tests/playwright/fixtures/test.ts +++ b/tests/playwright/fixtures/test.ts @@ -1,12 +1,5 @@ /* eslint-disable no-empty-pattern */ import { test as base, expect } from '@playwright/test' -import type { - Fixtures, - PlaywrightTestArgs, - PlaywrightTestOptions, - PlaywrightWorkerArgs, - PlaywrightWorkerOptions, -} from '@playwright/test' import { BrowserContext, ElectronApplication, @@ -15,33 +8,36 @@ import { } from 'playwright' import log from 'node-color-log' -const isElectron = process.env.ELECTRON_EXECUTABLE_PATH !== undefined +import { isElectron, electronExecutablePath } from '../helpers/conf' -base.beforeEach(async ({ page }) => { - if (!isElectron) { - await page.goto('/', { timeout: 5000 }) - } +const commonTest = base.extend<{ forEachTest: void }>({ + forEachTest: [ + async ({ page }, use) => { + // before each test: + if (!isElectron) { + await page.goto('/') + } - await page.waitForSelector('[aria-label="Main navigation"]', { - timeout: 10000, - }) + await page.waitForSelector('[aria-label="Main navigation"]', { + timeout: 2000, + }) + + await use() + + // after each test: + }, + { auto: true }, + ], }) -type ElectronTestFixtures = { - electronApp: ElectronApplication +const electronTest = commonTest.extend<{ + electronApp: ElectronApplication | null page: Page context: BrowserContext -} - -export const electronFixtures: Fixtures< - ElectronTestFixtures, - {}, - PlaywrightTestArgs & PlaywrightTestOptions, - PlaywrightWorkerArgs & PlaywrightWorkerOptions -> = { +}>({ electronApp: async ({}, use) => { const electronApp = await electron.launch({ - executablePath: process.env.ELECTRON_EXECUTABLE_PATH, + executablePath: electronExecutablePath, args: ['index.html'], timeout: 60000, }) @@ -58,20 +54,25 @@ export const electronFixtures: Fixtures< await electronApp.close() }, page: async ({ electronApp }, use) => { - const page = await electronApp.firstWindow() + if (!electronApp) { + throw new Error('Electron app is not initialized') + } + + const electronPage = await electronApp.firstWindow() - await use(page) + await use(electronPage) }, context: async ({ electronApp }, use) => { - const context = electronApp.context() + if (!electronApp) { + throw new Error('Electron app is not initialized') + } - await use(context) - }, -} + const electronContext = electronApp.context() -const electronTest = base.extend(electronFixtures) -const browserTest = base + await use(electronContext) + }, +}) -const test = isElectron ? electronTest : browserTest +const test = isElectron ? electronTest : commonTest export { test, expect, isElectron } diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts index 230f2cec9d..306d687069 100644 --- a/tests/playwright/helpers/conf.ts +++ b/tests/playwright/helpers/conf.ts @@ -7,6 +7,8 @@ import * as path from 'path' // Urls for using in the tests export const commonUrl = process.env.COMMON_URL || 'https://localhost:5540' export const apiUrl = process.env.API_URL || 'https://localhost:5540/api' +export const electronExecutablePath = process.env.ELECTRON_EXECUTABLE_PATH +export const isElectron = electronExecutablePath !== undefined export const googleUser = process.env.GOOGLE_USER || '' export const googleUserPassword = process.env.GOOGLE_USER_PASSWORD || '' export const samlUser = process.env.E2E_SSO_EMAIL || '' diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 216e6a3c72..6933a03dd3 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -16,11 +16,11 @@ "removeReportDirs": "rm -rf allure-results playwright-report test-results", "allTests": "playwright test", "generateReports": "allure generate --clean", - "test:chromium:docker": "cross-env envPath=env/.docker.env yarn playwright test --project=localChromium", + "test:chromium:docker": "cross-env envPath=env/.docker.env yarn playwright test --project=Chromium", "test:chromium:docker:debug": "yarn test:chromium:docker --debug", - "test:chromium:local-web": "cross-env envPath=env/.local-web.env yarn playwright test --project=localChromium", + "test:chromium:local-web": "cross-env envPath=env/.local-web.env yarn playwright test --project=Chromium", "test:chromium:local-web:debug": "yarn test:chromium:local-web --debug", - "test:electron": "cross-env envPath=env/.desktop.env yarn playwright test --project=localElectron", + "test:electron": "cross-env envPath=env/.desktop.env yarn playwright test --project=Chromium", "test:electron:debug": "yarn test:electron --debug", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 37ba265066..e821b112f6 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -33,7 +33,8 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + // workers: process.env.CI ? 1 : undefined, + workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [ ['line'], @@ -96,12 +97,11 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - name: 'localChromium', + name: 'Chromium', testMatch: ['**.spec.ts'], use: { ...devices['Desktop Chrome'], baseURL: process.env.COMMON_URL, - apiUrl: process.env.API_URL, // headless: false, launchOptions: { args: [ @@ -114,15 +114,6 @@ export default defineConfig({ }, }, }, - { - name: 'localElectron', - testMatch: ['**.spec.ts'], - workers: 1, - use: { - // baseURL: process.env.COMMON_URL, - apiUrl: process.env.API_URL, - }, - }, ], /* Run your local dev server before starting the tests */ diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index 9c6c1bef3b..a2a894857c 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -5,27 +5,27 @@ import { DatabaseHelper } from '../helpers/database' import { APIKeyRequests } from '../helpers/api/api-keys' import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { test, expect } from '../fixtures/test' -import { ossStandaloneConfig } from '../helpers/conf' +import { apiUrl, ossStandaloneConfig } from '../helpers/conf' let keyName: string let browserPage: BrowserPage let databaseHelper: DatabaseHelper -test.beforeEach(async ({ page }, testInfo) => { +test.beforeEach(async ({ page }) => { // await electronPage.getByText('Add Redis').click() browserPage = new BrowserPage(page) - databaseHelper = new DatabaseHelper(page, testInfo.project.use.apiUrl) + databaseHelper = new DatabaseHelper(page, apiUrl) await databaseHelper.acceptLicenseTermsAndAddDatabaseApi( ossStandaloneConfig, page, - testInfo.project.use.apiUrl, + apiUrl, ) keyName = Common.generateAlphanumeric(5) }) -test.afterEach(async ({}, testInfo) => { - const apiKeyClient = new APIKeyRequests(testInfo.project.use.apiUrl) - const dbApi = new DatabaseAPIRequests(testInfo.project.use.apiUrl) +test.afterEach(async ({}) => { + const apiKeyClient = new APIKeyRequests(apiUrl) + const dbApi = new DatabaseAPIRequests(apiUrl) await apiKeyClient.deleteKeyByNameApi( keyName, diff --git a/tests/playwright/tests/example2.spec.ts b/tests/playwright/tests/example2.spec.ts new file mode 100644 index 0000000000..d5414cbba2 --- /dev/null +++ b/tests/playwright/tests/example2.spec.ts @@ -0,0 +1,11 @@ +import { test, expect } from '../fixtures/test' + +test.describe('Basic Navigation and Element Visibility', () => { + test('should navigate to the homepage and verify title', async ({ + page, + }) => { + const title = await page.title() + + expect(title).toBe('Redis databases') + }) +}) From 46874dc18b05a10780f9075543cbe753dd89656f Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 15:24:06 +0300 Subject: [PATCH 093/128] update workflows --- .github/workflows/tests-e2e-playwright.yml | 2 ++ tests/playwright/playwright.config.ts | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index fd77694fca..695a876867 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -82,10 +82,12 @@ jobs: echo "RI_SERVER_TLS_KEY: $RI_SERVER_TLS_KEY" - name: Test connectivity to Redis Insight + continue-on-error: true run: | curl -v https://localhost:5540/ - name: Test connectivity to Redis Insight (no SSL) + continue-on-error: true run: | curl -v -k https://localhost:5540/ diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index e821b112f6..cbf77fa969 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -105,11 +105,13 @@ export default defineConfig({ // headless: false, launchOptions: { args: [ + '--no-sandbox', '--start-maximized', - '--disable-component-extensions-with-background-pages', '--disable-dev-shm-usage', - '--disable-blink-features=AutomationControlled', '--ignore-certificate-errors', + '--disable-search-engine-choice-screen', + // '--disable-blink-features=AutomationControlled', + // '--disable-component-extensions-with-background-pages', ], }, }, From 101de1db99ee42ee624198235d125b668c8e5edb Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 15:40:39 +0300 Subject: [PATCH 094/128] minor refactor --- tests/playwright/helpers/api/api-databases.ts | 98 ++++++++++++------- .../pageObjects/my-redis-databases-page.ts | 21 +--- tests/playwright/types/connections.ts | 4 +- tests/playwright/types/databes.ts | 51 ++++------ 4 files changed, 81 insertions(+), 93 deletions(-) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index bb2095ef4a..2f01c948d1 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -1,21 +1,22 @@ import { faker } from '@faker-js/faker' -import {AxiosInstance} from 'axios' +import { AxiosInstance } from 'axios' import { HttpClient } from './http-client' -import { AddNewDatabaseParameters, databaseParameters } from '../../types' +import { AddNewDatabaseParameters } from '../../types' import { ResourcePath } from '../constants' import { asyncFilter, doAsyncStuff } from '../async-helper' - export class DatabaseAPIRequests { - private apiClient: AxiosInstance constructor(apiUrl: string) { this.apiClient = new HttpClient(apiUrl).getClient() } - async addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, xWindowsId:string ,isCloud = false) : Promise { - + async addNewStandaloneDatabaseApi( + databaseParameters: AddNewDatabaseParameters, + xWindowsId: string, + isCloud = false, + ): Promise { const uniqueId = faker.string.alphanumeric({ length: 10 }) const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) const requestBody: any = { @@ -23,7 +24,7 @@ export class DatabaseAPIRequests { host: databaseParameters.host, port: Number(databaseParameters.port), username: databaseParameters.databaseUsername, - password: databaseParameters.databasePassword + password: databaseParameters.databasePassword, } if (databaseParameters.caCert) { @@ -31,12 +32,12 @@ export class DatabaseAPIRequests { requestBody.verifyServerCert = false requestBody.caCert = { name: `ca-${uniqueId}`, - certificate: databaseParameters.caCert.certificate + certificate: databaseParameters.caCert.certificate, } requestBody.clientCert = { name: `client-${uniqueId}`, certificate: databaseParameters.clientCert!.certificate, - key: databaseParameters.clientCert!.key + key: databaseParameters.clientCert!.key, } } @@ -46,53 +47,76 @@ export class DatabaseAPIRequests { subscriptionType: 'fixed', planMemoryLimit: 30, memoryLimitMeasurementUnit: 'mb', - free: true + free: true, } } - const response = await this.apiClient.post(ResourcePath.Databases, requestBody,{ - headers:{ - 'X-Window-Id': xWindowsId - } - }) - if (response.status !== 201) throw new Error(`Database creation failed for ${databaseParameters.databaseName}`) + const response = await this.apiClient.post( + ResourcePath.Databases, + requestBody, + { + headers: { + 'X-Window-Id': xWindowsId, + }, + }, + ) + if (response.status !== 201) + throw new Error( + `Database creation failed for ${databaseParameters.databaseName}`, + ) } - async getAllDatabases(xWindowsId:string): Promise { - - const response = await this.apiClient.get(ResourcePath.Databases,{ - headers:{ - 'X-Window-Id': xWindowsId - } + async getAllDatabases(xWindowsId: string): Promise { + const response = await this.apiClient.get(ResourcePath.Databases, { + headers: { + 'X-Window-Id': xWindowsId, + }, }) - if (response.status !== 200) throw new Error('Failed to retrieve databases') + if (response.status !== 200) + throw new Error('Failed to retrieve databases') return response.data } - async getDatabaseIdByName(databaseName?: string, xWindowsId: string): Promise { + async getDatabaseIdByName( + databaseName?: string, + xWindowsId: string, + ): Promise { if (!databaseName) throw new Error('Error: Missing databaseName') const allDatabases = await this.getAllDatabases(xWindowsId) - const filteredDb = await asyncFilter(allDatabases, async (item: databaseParameters) => { - await doAsyncStuff() - return item.name === databaseName - }) - - if (filteredDb.length === 0) throw new Error(`Database ${databaseName} not found`) + const filteredDb = await asyncFilter( + allDatabases, + async (item: databaseParameters) => { + await doAsyncStuff() + return item.name === databaseName + }, + ) + + if (filteredDb.length === 0) + throw new Error(`Database ${databaseName} not found`) return filteredDb[0].id } - async deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters, xWindowsId: string): Promise { - const databaseId = await this.getDatabaseIdByName(databaseParameters.databaseName, xWindowsId) + async deleteStandaloneDatabaseApi( + databaseParameters: AddNewDatabaseParameters, + xWindowsId: string, + ): Promise { + const databaseId = await this.getDatabaseIdByName( + databaseParameters.databaseName, + xWindowsId, + ) if (!databaseId) throw new Error('Error: Missing databaseId') const requestBody = { ids: [databaseId] } const response = await this.apiClient.delete(ResourcePath.Databases, { data: requestBody, - headers:{ - 'X-Window-Id': xWindowsId - }}) - if (response.status !== 200) throw new Error(`Failed to delete database ${databaseParameters.databaseName}`) + headers: { + 'X-Window-Id': xWindowsId, + }, + }) + if (response.status !== 200) + throw new Error( + `Failed to delete database ${databaseParameters.databaseName}`, + ) } } - diff --git a/tests/playwright/pageObjects/my-redis-databases-page.ts b/tests/playwright/pageObjects/my-redis-databases-page.ts index fa6568d452..4b1198912b 100755 --- a/tests/playwright/pageObjects/my-redis-databases-page.ts +++ b/tests/playwright/pageObjects/my-redis-databases-page.ts @@ -8,26 +8,7 @@ import { Toast } from './components/common/toast' import { AddRedisDatabaseDialog } from './dialogs/add-redis-database-dialog' import {DatabaseAPIRequests} from '../helpers/api/api-databases'; // import { DatabaseAPIRequests } from '../helpers/api/api-database' - -export type DatabasesForImport = { - host?: string, - port?: number | string, - name?: string, - result?: string, - username?: string, - auth?: string, - cluster?: boolean | string, - indName?: string, - db?: number, - ssh_port?: number, - timeout_connect?: number, - timeout_execute?: number, - other_field?: string, - ssl?: boolean, - ssl_ca_cert_path?: string, - ssl_local_cert_path?: string, - ssl_private_key_path?: string -}[] +import { DatabasesForImport } from '../types' export class MyRedisDatabasePage extends BaseOverviewPage { // // Component Instances diff --git a/tests/playwright/types/connections.ts b/tests/playwright/types/connections.ts index 6a82170fe9..2da226ee93 100644 --- a/tests/playwright/types/connections.ts +++ b/tests/playwright/types/connections.ts @@ -32,6 +32,6 @@ export type SSHParameters = { * @param port The port of the node */ export type ClusterNodes = { - host: string, + host: string port: string -}; +} diff --git a/tests/playwright/types/databes.ts b/tests/playwright/types/databes.ts index 7bd6495758..8408771e1a 100644 --- a/tests/playwright/types/databes.ts +++ b/tests/playwright/types/databes.ts @@ -1,22 +1,23 @@ export type DatabasesForImport = { - host?: string, - port?: number | string, - name?: string, - result?: string, - username?: string, - auth?: string, - cluster?: boolean | string, - indName?: string, - db?: number, - ssh_port?: number, - timeout_connect?: number, - timeout_execute?: number, - other_field?: string, - ssl?: boolean, - ssl_ca_cert_path?: string, - ssl_local_cert_path?: string, + host?: string + port?: number | string + name?: string + result?: string + username?: string + auth?: string + cluster?: boolean | string + indName?: string + db?: number + ssh_port?: number + timeout_connect?: number + timeout_execute?: number + other_field?: string + ssl?: boolean + ssl_ca_cert_path?: string + ssl_local_cert_path?: string ssl_private_key_path?: string }[] + export type AddNewDatabaseParameters = { host: string port: string @@ -37,21 +38,3 @@ export type AddNewDatabaseParameters = { key?: string } } - -/** - * Already existing database parameters - * @param id The id of the database - * @param host The host of the database - * @param port The port of the database - * @param name The name of the database - * @param connectionType The connection type of the database - * @param lastConnection The last connection time of the database - */ -export type databaseParameters = { - id: string, - host?: string, - port?: string, - name?: string, - connectionType?: string, - lastConnection?: string -} From 671be83eb6dbdc031aeb9c2c65f299a9a25de8ad Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 16:17:05 +0300 Subject: [PATCH 095/128] update workflow --- .github/workflows/tests-e2e-playwright.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 695a876867..65502ca1d0 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -69,17 +69,17 @@ jobs: docker compose -p e2e-ri-docker \ -f tests/e2e/docker.web.docker-compose.yml \ up --detach --force-recreate + sleep 30 - name: Debug docker containers run: | docker ps - - name: Debug env variables + - name: Wait for Redis Insight to be ready run: | - echo "RI_ENCRYPTION_KEY: $RI_ENCRYPTION_KEY" - echo "E2E_RI_ENCRYPTION_KEY: $E2E_RI_ENCRYPTION_KEY" - echo "RI_SERVER_TLS_CERT: $RI_SERVER_TLS_CERT" - echo "RI_SERVER_TLS_KEY: $RI_SERVER_TLS_KEY" + for i in {1..30}; do + curl -v -k https://localhost:5540/ && break || sleep 5 + done - name: Test connectivity to Redis Insight continue-on-error: true From cc327cb2237f2b6582680f6ffe8588bbdde173ae Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 16:48:39 +0300 Subject: [PATCH 096/128] update workflow --- .github/workflows/tests-e2e-playwright.yml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 65502ca1d0..0c74fcd89e 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -75,17 +75,6 @@ jobs: run: | docker ps - - name: Wait for Redis Insight to be ready - run: | - for i in {1..30}; do - curl -v -k https://localhost:5540/ && break || sleep 5 - done - - - name: Test connectivity to Redis Insight - continue-on-error: true - run: | - curl -v https://localhost:5540/ - - name: Test connectivity to Redis Insight (no SSL) continue-on-error: true run: | @@ -102,8 +91,11 @@ jobs: if: ${{ !cancelled() }} with: name: playwright-report - path: playwright-report/ - retention-days: 30 + path: | + test-results + allure-results + playwright-report + retention-days: 10 - name: Clean up redis test environments if: always() From 71f5237bbb31f9dbf24676afe8d7526d822a7beb Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 16:57:02 +0300 Subject: [PATCH 097/128] update tests cleanup --- tests/playwright/helpers/api/api-databases.ts | 1 + tests/playwright/tests/example.spec.ts | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 2f01c948d1..8a8e8eac94 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -84,6 +84,7 @@ export class DatabaseAPIRequests { if (!databaseName) throw new Error('Error: Missing databaseName') const allDatabases = await this.getAllDatabases(xWindowsId) + console.log('+++databases+++', databaseName, xWindowsId, allDatabases) const filteredDb = await asyncFilter( allDatabases, async (item: databaseParameters) => { diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts index a2a894857c..fe7609a7ac 100644 --- a/tests/playwright/tests/example.spec.ts +++ b/tests/playwright/tests/example.spec.ts @@ -27,15 +27,19 @@ test.afterEach(async ({}) => { const apiKeyClient = new APIKeyRequests(apiUrl) const dbApi = new DatabaseAPIRequests(apiUrl) - await apiKeyClient.deleteKeyByNameApi( - keyName, - ossStandaloneConfig.databaseName, - await browserPage.getWindowId(), - ) - await dbApi.deleteStandaloneDatabaseApi( - ossStandaloneConfig, - await browserPage.getWindowId(), - ) + try { + await apiKeyClient.deleteKeyByNameApi( + keyName, + ossStandaloneConfig.databaseName, + await browserPage.getWindowId(), + ) + await dbApi.deleteStandaloneDatabaseApi( + ossStandaloneConfig, + await browserPage.getWindowId(), + ) + } catch (error) { + console.warn('Error during cleanup:', error) + } }) test('basic test', async () => { From b9e982122b2aa118ceb73f1ed064bb78d0d84827 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 18:15:44 +0300 Subject: [PATCH 098/128] update upload path --- .github/workflows/tests-e2e-playwright.yml | 6 +++--- tests/playwright/helpers/api/api-databases.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 0c74fcd89e..18a0ce590b 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -92,9 +92,9 @@ jobs: with: name: playwright-report path: | - test-results - allure-results - playwright-report + ./test/playwright/test-results + ./test/playwright/allure-results + ./test/playwright/playwright-report retention-days: 10 - name: Clean up redis test environments diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 8a8e8eac94..e663315c2e 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -60,6 +60,7 @@ export class DatabaseAPIRequests { }, }, ) + console.log('+++added DB', response.data) if (response.status !== 201) throw new Error( `Database creation failed for ${databaseParameters.databaseName}`, From 3cd028a4e20fe1b781d93af90d2e5f24d13c9802 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 18:53:50 +0300 Subject: [PATCH 099/128] update artifacts path --- .github/workflows/tests-e2e-playwright.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 18a0ce590b..d168c97c5e 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -92,9 +92,9 @@ jobs: with: name: playwright-report path: | - ./test/playwright/test-results - ./test/playwright/allure-results - ./test/playwright/playwright-report + ./tests/playwright/test-results + ./tests/playwright/allure-results + ./tests/playwright/playwright-report retention-days: 10 - name: Clean up redis test environments From 8fb4e8c8aa601dfb3edd3bce46807af262ce52c3 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 20:39:08 +0300 Subject: [PATCH 100/128] update image build script --- tests/e2e/docker.web.docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/e2e/docker.web.docker-compose.yml b/tests/e2e/docker.web.docker-compose.yml index 2f1188bdb8..1c81333cb1 100644 --- a/tests/e2e/docker.web.docker-compose.yml +++ b/tests/e2e/docker.web.docker-compose.yml @@ -44,6 +44,9 @@ services: image: redisinsight:amd64 env_file: - ./.env + entrypoint: [ + './upload-custom-plugins.sh', + ] environment: RI_ENCRYPTION_KEY: $E2E_RI_ENCRYPTION_KEY RI_SERVER_TLS_CERT: $RI_SERVER_TLS_CERT From 786bcc09da44949546e69f92508265addd427810 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 21:24:53 +0300 Subject: [PATCH 101/128] update docker compose --- tests/e2e/docker.web.docker-compose.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/e2e/docker.web.docker-compose.yml b/tests/e2e/docker.web.docker-compose.yml index 1c81333cb1..3b57cc8437 100644 --- a/tests/e2e/docker.web.docker-compose.yml +++ b/tests/e2e/docker.web.docker-compose.yml @@ -44,9 +44,8 @@ services: image: redisinsight:amd64 env_file: - ./.env - entrypoint: [ - './upload-custom-plugins.sh', - ] + command: > + bash -c "./upload-custom-plugins.sh && ./docker-entry.sh" environment: RI_ENCRYPTION_KEY: $E2E_RI_ENCRYPTION_KEY RI_SERVER_TLS_CERT: $RI_SERVER_TLS_CERT From 3e461193f08aa69e64720596cd424508c1c9d867 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 22:37:43 +0300 Subject: [PATCH 102/128] update scripts --- tests/playwright/helpers/conf.ts | 1 + tests/playwright/helpers/electron/database-scripts.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts index 306d687069..5637913f74 100644 --- a/tests/playwright/helpers/conf.ts +++ b/tests/playwright/helpers/conf.ts @@ -17,6 +17,7 @@ export const samlUserPassword = process.env.E2E_SSO_PASSWORD || '' export const workingDirectory = process.env.RI_APP_FOLDER_ABSOLUTE_PATH || joinPath(os.homedir(), process.env.RI_APP_FOLDER_NAME || '.redis-insight') +console.log('+++workingDirectory+++', workingDirectory) export const fileDownloadPath = joinPath(os.homedir(), 'Downloads') const uniqueId = faker.string.alphanumeric({ length: 10 }) diff --git a/tests/playwright/helpers/electron/database-scripts.ts b/tests/playwright/helpers/electron/database-scripts.ts index e21641eb36..3518b27099 100755 --- a/tests/playwright/helpers/electron/database-scripts.ts +++ b/tests/playwright/helpers/electron/database-scripts.ts @@ -4,6 +4,7 @@ import {workingDirectory} from '../conf' import {createTimeout} from '../utils' const dbPath = `${workingDirectory}/redisinsight.db` +console.log('+++dbPath+++', dbPath) export class DatabaseScripts { /** @@ -20,9 +21,9 @@ export class DatabaseScripts { await runAsync(query, [dbTableParameters.rowValue, dbTableParameters.conditionWhereColumnValue]) } catch (err) { console.log(`Error during changing ${dbTableParameters.columnName} column value: ${err}`) - throw new Error( - `Error during changing ${dbTableParameters.columnName} column value: ${err}`, - ) + // throw new Error( + // `Error during changing ${dbTableParameters.columnName} column value: ${err}`, + // ) } finally { console.log('Close DB') db.close() From 98f00e1d9ab26bee87c122a3c3f8690d89a8140d Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 6 Jun 2025 23:21:45 +0300 Subject: [PATCH 103/128] disable sqlite edit --- tests/playwright/helpers/electron/insights.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/helpers/electron/insights.ts b/tests/playwright/helpers/electron/insights.ts index 8f9ac39cfa..e6fc271461 100755 --- a/tests/playwright/helpers/electron/insights.ts +++ b/tests/playwright/helpers/electron/insights.ts @@ -34,7 +34,7 @@ export async function modifyFeaturesConfigJson(filePath: string): Promise */ export async function updateControlNumber(controlNumber: number, apiUrl: string, xWindowId:string): Promise { await syncFeaturesApi(apiUrl, xWindowId) - await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }) + // await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }) await syncFeaturesApi(apiUrl, xWindowId) } From b4cf555659eedc2b6d1d0154c37fc96fe34e09e6 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 00:30:42 +0300 Subject: [PATCH 104/128] debug api --- .github/workflows/tests-e2e-playwright.yml | 1 + tests/playwright/helpers/api/api-databases.ts | 3 +++ tests/playwright/helpers/api/http-client.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index d168c97c5e..a6810e839b 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -79,6 +79,7 @@ jobs: continue-on-error: true run: | curl -v -k https://localhost:5540/ + curl -k https://localhost:5540/api/databases - name: Run Playwright tests timeout-minutes: 80 diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index e663315c2e..8b5521c3ec 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -51,6 +51,9 @@ export class DatabaseAPIRequests { } } + const databasesResponse = await this.getAllDatabases(xWindowsId) + console.log('+++databasesResponse+++', databasesResponse) + const response = await this.apiClient.post( ResourcePath.Databases, requestBody, diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index f035b073ed..ba7fb3c847 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -12,6 +12,7 @@ export class HttpClient { constructor(apiUrl: string) { this.apiUrl = apiUrl + console.log('+++apiUrl+++', this.apiUrl) this.apiClient = axios.create({ baseURL: this.apiUrl, headers: {}, From ef70cf2eccda909c7d6e0ca51b0989c4c92ac67f Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 00:58:54 +0300 Subject: [PATCH 105/128] update workflow --- .github/workflows/tests-e2e-playwright.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index a6810e839b..c3c535c80c 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -78,7 +78,6 @@ jobs: - name: Test connectivity to Redis Insight (no SSL) continue-on-error: true run: | - curl -v -k https://localhost:5540/ curl -k https://localhost:5540/api/databases - name: Run Playwright tests From 5275b78212f0e8dcebbd73209d614977b93a1fea Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 11:49:29 +0300 Subject: [PATCH 106/128] debug post request --- tests/playwright/helpers/api/api-databases.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 8b5521c3ec..a6905e8bcf 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -53,6 +53,10 @@ export class DatabaseAPIRequests { const databasesResponse = await this.getAllDatabases(xWindowsId) console.log('+++databasesResponse+++', databasesResponse) + console.log('+++requestBody+++', xWindowsId, requestBody) + + // wait 2s + await new Promise((resolve) => setTimeout(resolve, 2000)) const response = await this.apiClient.post( ResourcePath.Databases, From 92da15258b0ae53d38e3ecae5cabebc90d277ecf Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 13:09:47 +0300 Subject: [PATCH 107/128] update db insert --- .github/workflows/tests-e2e-playwright.yml | 11 +++++++++++ tests/playwright/helpers/api/api-databases.ts | 14 ++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index c3c535c80c..9bbcc77c88 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -78,6 +78,11 @@ jobs: - name: Test connectivity to Redis Insight (no SSL) continue-on-error: true run: | + echo "Testing GET request" + curl -k https://localhost:5540/api/databases + echo "Testing POST request" + curl -k -X POST https://localhost:5540/api/databases -d '{"name":"test_standalone-u2mqMSWsGw", "host":"host.docker.internal", "port":8100, "username":"default", "timeout":30000, "tls":false }' -H "Content-Type: application/json" + echo "Testing GET request" curl -k https://localhost:5540/api/databases - name: Run Playwright tests @@ -87,6 +92,12 @@ jobs: run: | yarn test:chromium:docker + - name: Test connectivity to Redis Insight again (no SSL) + if: always() + continue-on-error: true + run: | + curl -k https://localhost:5540/api/databases + - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index a6905e8bcf..676e24c1d8 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -23,8 +23,14 @@ export class DatabaseAPIRequests { name: databaseParameters.databaseName, host: databaseParameters.host, port: Number(databaseParameters.port), - username: databaseParameters.databaseUsername, - password: databaseParameters.databasePassword, + } + + if (databaseParameters.databaseUsername) { + requestBody.username = databaseParameters.databaseUsername + } + + if (databaseParameters.databasePassword) { + requestBody.password = databaseParameters.databasePassword } if (databaseParameters.caCert) { @@ -53,10 +59,6 @@ export class DatabaseAPIRequests { const databasesResponse = await this.getAllDatabases(xWindowsId) console.log('+++databasesResponse+++', databasesResponse) - console.log('+++requestBody+++', xWindowsId, requestBody) - - // wait 2s - await new Promise((resolve) => setTimeout(resolve, 2000)) const response = await this.apiClient.post( ResourcePath.Databases, From 1cf220d31e14b6d0161794a34ca49f183383363b Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 13:59:34 +0300 Subject: [PATCH 108/128] update hosts --- .github/workflows/tests-e2e-playwright.yml | 9 +++++++-- tests/e2e/docker.web.docker-compose.yml | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 9bbcc77c88..07b90bb620 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -75,13 +75,18 @@ jobs: run: | docker ps + - name: Get Host IP Address + run: echo "HOST_IP=$(ip route | awk '/default/ { print $3 }')" >> $GITHUB_ENV + - name: Test connectivity to Redis Insight (no SSL) continue-on-error: true run: | echo "Testing GET request" curl -k https://localhost:5540/api/databases - echo "Testing POST request" - curl -k -X POST https://localhost:5540/api/databases -d '{"name":"test_standalone-u2mqMSWsGw", "host":"host.docker.internal", "port":8100, "username":"default", "timeout":30000, "tls":false }' -H "Content-Type: application/json" + echo "Testing POST request 1" + curl -k -X POST https://localhost:5540/api/databases -d '{"name":"test_standalone-1", "host":"host.docker.internal", "port":8100, "username":"default", "timeout":30000, "tls":false }' -H "Content-Type: application/json" + echo "Testing POST request 2" + curl -k -X POST https://localhost:5540/api/databases -d '{"name":"test_standalone-2", "host":"$HOST_IP", "port":8100, "username":"default", "timeout":30000, "tls":false }' -H "Content-Type: application/json" echo "Testing GET request" curl -k https://localhost:5540/api/databases diff --git a/tests/e2e/docker.web.docker-compose.yml b/tests/e2e/docker.web.docker-compose.yml index 3b57cc8437..bf71ee275f 100644 --- a/tests/e2e/docker.web.docker-compose.yml +++ b/tests/e2e/docker.web.docker-compose.yml @@ -39,6 +39,8 @@ services: # Built image app: + extra_hosts: + - "host.docker.internal:host-gateway" logging: driver: none image: redisinsight:amd64 From 527666faa9e544ad283f0288553271fdca08432b Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 20:09:32 +0300 Subject: [PATCH 109/128] cleanup logs --- tests/playwright/helpers/api/api-databases.ts | 5 ----- tests/playwright/helpers/api/http-client.ts | 1 - tests/playwright/helpers/conf.ts | 1 - tests/playwright/helpers/electron/database-scripts.ts | 1 - 4 files changed, 8 deletions(-) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index 676e24c1d8..bac5539266 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -57,9 +57,6 @@ export class DatabaseAPIRequests { } } - const databasesResponse = await this.getAllDatabases(xWindowsId) - console.log('+++databasesResponse+++', databasesResponse) - const response = await this.apiClient.post( ResourcePath.Databases, requestBody, @@ -69,7 +66,6 @@ export class DatabaseAPIRequests { }, }, ) - console.log('+++added DB', response.data) if (response.status !== 201) throw new Error( `Database creation failed for ${databaseParameters.databaseName}`, @@ -94,7 +90,6 @@ export class DatabaseAPIRequests { if (!databaseName) throw new Error('Error: Missing databaseName') const allDatabases = await this.getAllDatabases(xWindowsId) - console.log('+++databases+++', databaseName, xWindowsId, allDatabases) const filteredDb = await asyncFilter( allDatabases, async (item: databaseParameters) => { diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index ba7fb3c847..f035b073ed 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -12,7 +12,6 @@ export class HttpClient { constructor(apiUrl: string) { this.apiUrl = apiUrl - console.log('+++apiUrl+++', this.apiUrl) this.apiClient = axios.create({ baseURL: this.apiUrl, headers: {}, diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts index 5637913f74..306d687069 100644 --- a/tests/playwright/helpers/conf.ts +++ b/tests/playwright/helpers/conf.ts @@ -17,7 +17,6 @@ export const samlUserPassword = process.env.E2E_SSO_PASSWORD || '' export const workingDirectory = process.env.RI_APP_FOLDER_ABSOLUTE_PATH || joinPath(os.homedir(), process.env.RI_APP_FOLDER_NAME || '.redis-insight') -console.log('+++workingDirectory+++', workingDirectory) export const fileDownloadPath = joinPath(os.homedir(), 'Downloads') const uniqueId = faker.string.alphanumeric({ length: 10 }) diff --git a/tests/playwright/helpers/electron/database-scripts.ts b/tests/playwright/helpers/electron/database-scripts.ts index 3518b27099..ae2e7e8cf1 100755 --- a/tests/playwright/helpers/electron/database-scripts.ts +++ b/tests/playwright/helpers/electron/database-scripts.ts @@ -4,7 +4,6 @@ import {workingDirectory} from '../conf' import {createTimeout} from '../utils' const dbPath = `${workingDirectory}/redisinsight.db` -console.log('+++dbPath+++', dbPath) export class DatabaseScripts { /** From bae248e3b8c271d9022660553d97d1d481d4ea24 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 20:33:45 +0300 Subject: [PATCH 110/128] extract e2e workflow --- .github/workflows/tests-e2e-playwright.yml | 32 -------- .github/workflows/tests-e2e.yml | 88 ++++++++++++++++++++++ .github/workflows/tests.yml | 82 +------------------- 3 files changed, 91 insertions(+), 111 deletions(-) create mode 100644 .github/workflows/tests-e2e.yml diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 07b90bb620..30dbc20103 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -1,11 +1,5 @@ name: Playwright E2E Tests on: - # push: - # branches: - # - main - # pull_request: - # branches: - # - main workflow_call: env: @@ -33,7 +27,6 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v4 - # - uses: ./.github/actions/install-all-build-libs - name: Install dependencies for Playwright tests uses: ./.github/actions/install-deps @@ -71,25 +64,6 @@ jobs: up --detach --force-recreate sleep 30 - - name: Debug docker containers - run: | - docker ps - - - name: Get Host IP Address - run: echo "HOST_IP=$(ip route | awk '/default/ { print $3 }')" >> $GITHUB_ENV - - - name: Test connectivity to Redis Insight (no SSL) - continue-on-error: true - run: | - echo "Testing GET request" - curl -k https://localhost:5540/api/databases - echo "Testing POST request 1" - curl -k -X POST https://localhost:5540/api/databases -d '{"name":"test_standalone-1", "host":"host.docker.internal", "port":8100, "username":"default", "timeout":30000, "tls":false }' -H "Content-Type: application/json" - echo "Testing POST request 2" - curl -k -X POST https://localhost:5540/api/databases -d '{"name":"test_standalone-2", "host":"$HOST_IP", "port":8100, "username":"default", "timeout":30000, "tls":false }' -H "Content-Type: application/json" - echo "Testing GET request" - curl -k https://localhost:5540/api/databases - - name: Run Playwright tests timeout-minutes: 80 working-directory: ./tests/playwright @@ -97,12 +71,6 @@ jobs: run: | yarn test:chromium:docker - - name: Test connectivity to Redis Insight again (no SSL) - if: always() - continue-on-error: true - run: | - curl -k https://localhost:5540/api/databases - - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml new file mode 100644 index 0000000000..3b1a1e223e --- /dev/null +++ b/.github/workflows/tests-e2e.yml @@ -0,0 +1,88 @@ +name: ✅ E2E Tests + +on: + pull_request_review: + types: [submitted] + + workflow_dispatch: + inputs: + debug: + description: Enable SSH Debug (IT and E2E) + default: false + type: boolean + +# Cancel a previous run workflow +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # # E2E Approve + e2e-approve: + runs-on: ubuntu-latest + if: github.event.review.state == 'approved' || github.event_name == 'workflow_dispatch' + name: Approve E2E tests + + # E2E Docker + build-docker: + uses: ./.github/workflows/pipeline-build-docker.yml + needs: e2e-approve + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + for_e2e_tests: true + + e2e-docker-tests: + needs: build-docker + uses: ./.github/workflows/tests-e2e-docker.yml + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + + tests-e2e-playwright: + needs: build-docker + uses: ./.github/workflows/tests-e2e-playwright.yml + secrets: inherit + # with: + # debug: ${{ inputs.debug || false }} + + # E2E AppImage + build-appimage: + uses: ./.github/workflows/pipeline-build-linux.yml + needs: e2e-approve + secrets: inherit + with: + target: build_linux_appimage_x64 + debug: ${{ inputs.debug || false }} + + e2e-appimage-tests: + needs: build-appimage + uses: ./.github/workflows/tests-e2e-appimage.yml + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + + clean: + uses: ./.github/workflows/clean-deployments.yml + if: always() + needs: + [ + e2e-docker-tests, + e2e-appimage-tests, + tests-e2e-playwright, + ] + + # Remove artifacts from github actions + remove-artifacts: + name: Remove artifacts + needs: + [ + e2e-docker-tests, + e2e-appimage-tests, + tests-e2e-playwright, + ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Remove all artifacts + uses: ./.github/actions/remove-artifacts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e0efa8f155..e65264f9ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,22 +6,12 @@ on: - 'fe/**' - 'be/**' - 'fe-be/**' - - 'e2e/**' - 'feature/**' - 'bugfix/**' - 'ric/**' workflow_dispatch: inputs: - group_tests: - description: Run group of tests - default: 'all' - type: choice - options: - - all - - without_e2e - - only_e2e - redis_client: description: Library to use for redis connection default: 'ioredis' @@ -42,10 +32,6 @@ on: workflow_call: inputs: - group_tests: - description: Run group of tests - type: string - default: 'without_e2e' short_rte_list: description: Use short rte list type: boolean @@ -71,8 +57,6 @@ jobs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} desktop: ${{ steps.filter.outputs.desktop }} - e2e: ${{ steps.filter.outputs.e2e }} - e2ePlaywright: ${{ steps.filter.outputs.e2ePlaywright }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3.0.2 @@ -86,14 +70,10 @@ jobs: - 'redisinsight/api/**' desktop: - 'redisinsight/desktop/**' - e2e: - - 'tests/e2e/**' - e2ePlaywright: - - 'tests/playwright/**' frontend-tests: needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'fe/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') + if: startsWith(github.ref_name, 'fe/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') uses: ./.github/workflows/tests-frontend.yml secrets: inherit @@ -107,7 +87,7 @@ jobs: backend-tests: needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') + if: startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') uses: ./.github/workflows/tests-backend.yml secrets: inherit @@ -121,7 +101,7 @@ jobs: integration-tests: needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') + if: startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') uses: ./.github/workflows/tests-integration.yml secrets: inherit with: @@ -137,58 +117,6 @@ jobs: resource_name: integration-coverage type: integration - # # E2E Approve - e2e-approve: - runs-on: ubuntu-latest - needs: changes - # if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') - timeout-minutes: 60 - environment: ${{ startsWith(github.ref_name, 'e2e/') && 'e2e-approve' || 'staging' }} - name: Approve E2E tests - steps: - - uses: actions/checkout@v4 - - # E2E Docker - build-docker: - uses: ./.github/workflows/pipeline-build-docker.yml - needs: e2e-approve - secrets: inherit - with: - debug: ${{ inputs.debug || false }} - for_e2e_tests: true - - e2e-docker-tests: - if: inputs.group_tests == 'never' - needs: build-docker - uses: ./.github/workflows/tests-e2e-docker.yml - secrets: inherit - with: - debug: ${{ inputs.debug || false }} - - tests-e2e-playwright-docker: - needs: build-docker - uses: ./.github/workflows/tests-e2e-playwright.yml - secrets: inherit - # with: - # debug: ${{ inputs.debug || false }} - - # E2E AppImage - build-appimage: - if: inputs.group_tests == 'never' - uses: ./.github/workflows/pipeline-build-linux.yml - needs: e2e-approve - secrets: inherit - with: - target: build_linux_appimage_x64 - debug: ${{ inputs.debug || false }} - - e2e-appimage-tests: - needs: build-appimage - uses: ./.github/workflows/tests-e2e-appimage.yml - secrets: inherit - with: - debug: ${{ inputs.debug || false }} - clean: uses: ./.github/workflows/clean-deployments.yml if: always() @@ -197,8 +125,6 @@ jobs: frontend-tests, backend-tests, integration-tests, - e2e-docker-tests, - e2e-appimage-tests, ] # Remove artifacts from github actions @@ -209,8 +135,6 @@ jobs: frontend-tests, backend-tests, integration-tests, - e2e-docker-tests, - e2e-appimage-tests, ] runs-on: ubuntu-latest steps: From 3d53045c14fd2e625b8dfa9b3aadfbff2ec9c2e7 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 20:38:47 +0300 Subject: [PATCH 111/128] add steps for e2e job --- .github/workflows/tests-e2e.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index 3b1a1e223e..ed1b7fd072 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -22,6 +22,8 @@ jobs: runs-on: ubuntu-latest if: github.event.review.state == 'approved' || github.event_name == 'workflow_dispatch' name: Approve E2E tests + steps: + - run: echo "Proceeding with E2E tests" # E2E Docker build-docker: From f3ec9241270bf96db79e8f0ba42a4410cf6106c1 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 20:45:32 +0300 Subject: [PATCH 112/128] add temporal push trigger for e2e --- .github/workflows/tests-e2e.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index ed1b7fd072..c9cb4687ca 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -1,6 +1,16 @@ name: ✅ E2E Tests on: + # TODO: remove push trigger when workflow is added to main + push: + branches: + - 'fe/**' + - 'be/**' + - 'fe-be/**' + - 'feature/**' + - 'bugfix/**' + - 'ric/**' + pull_request_review: types: [submitted] From 69444e8391c97eb504b06c0223c20b2c97e3b08c Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 20:47:34 +0300 Subject: [PATCH 113/128] add TODO comment --- .github/workflows/tests-e2e.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index c9cb4687ca..dbf1bf7829 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -30,7 +30,8 @@ jobs: # # E2E Approve e2e-approve: runs-on: ubuntu-latest - if: github.event.review.state == 'approved' || github.event_name == 'workflow_dispatch' + # TODO: uncomment when on push trigger is removed + # if: github.event.review.state == 'approved' || github.event_name == 'workflow_dispatch' name: Approve E2E tests steps: - run: echo "Proceeding with E2E tests" From ff0f1122abcd51d48120d6691e336236d01ac0a3 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 21:27:46 +0300 Subject: [PATCH 114/128] adjust e2e tests workflow trigger --- .github/workflows/code-coverage.yml | 1 + .github/workflows/tests-e2e.yml | 17 +++++++---------- .github/workflows/tests.yml | 7 +++++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index b437b0d023..b3d5b0ab2a 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -100,6 +100,7 @@ jobs: id: findPr - name: Post or Update Coverage Summary Comment + if: ${{ steps.findPr.outputs.number != '' }} uses: actions/github-script@v7 with: script: | diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index dbf1bf7829..ca9c452b49 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -1,16 +1,6 @@ name: ✅ E2E Tests on: - # TODO: remove push trigger when workflow is added to main - push: - branches: - - 'fe/**' - - 'be/**' - - 'fe-be/**' - - 'feature/**' - - 'bugfix/**' - - 'ric/**' - pull_request_review: types: [submitted] @@ -21,6 +11,13 @@ on: default: false type: boolean + workflow_call: + inputs: + debug: + description: Enable SSH Debug + default: false + type: boolean + # Cancel a previous run workflow concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e65264f9ab..d3faf5f05f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -117,6 +117,13 @@ jobs: resource_name: integration-coverage type: integration + # TODO: remove this after ✅ E2E Tests workflow is available in main + e2e-tests: + uses: ./.github/workflows/tests-e2e.yml + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + clean: uses: ./.github/workflows/clean-deployments.yml if: always() From 4884d4b77cf14677dd8a6266c6173bce4d1fc25d Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 22:25:11 +0300 Subject: [PATCH 115/128] revert temp changes --- tests/e2e/docker.web.docker-compose.yml | 2 -- tests/e2e/local.web.docker-compose.yml | 2 +- tests/e2e/rte.docker-compose.yml | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/e2e/docker.web.docker-compose.yml b/tests/e2e/docker.web.docker-compose.yml index bf71ee275f..1aa501ad44 100644 --- a/tests/e2e/docker.web.docker-compose.yml +++ b/tests/e2e/docker.web.docker-compose.yml @@ -46,8 +46,6 @@ services: image: redisinsight:amd64 env_file: - ./.env - command: > - bash -c "./upload-custom-plugins.sh && ./docker-entry.sh" environment: RI_ENCRYPTION_KEY: $E2E_RI_ENCRYPTION_KEY RI_SERVER_TLS_CERT: $RI_SERVER_TLS_CERT diff --git a/tests/e2e/local.web.docker-compose.yml b/tests/e2e/local.web.docker-compose.yml index 985ef0d01f..b889d935d0 100644 --- a/tests/e2e/local.web.docker-compose.yml +++ b/tests/e2e/local.web.docker-compose.yml @@ -41,7 +41,7 @@ services: app: logging: driver: none - image: redisinsight:arm64 + image: redisinsight:amd64 env_file: - ./.env environment: diff --git a/tests/e2e/rte.docker-compose.yml b/tests/e2e/rte.docker-compose.yml index f0f5cdbf94..ba425cd5a1 100644 --- a/tests/e2e/rte.docker-compose.yml +++ b/tests/e2e/rte.docker-compose.yml @@ -3,7 +3,7 @@ version: "3.4" services: static-server: logging: &logging - driver: json-file + driver: none build: context: . dockerfile: static-server.Dockerfile From 0f2d513db29dc1275078d48e3ccff4f27f7c205a Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sat, 7 Jun 2025 23:10:31 +0300 Subject: [PATCH 116/128] update workflows --- .github/workflows/tests-e2e.yml | 2 +- .github/workflows/tests.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index ca9c452b49..3d1943af3d 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -20,7 +20,7 @@ on: # Cancel a previous run workflow concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-e2e cancel-in-progress: true jobs: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d3faf5f05f..6c8fe7258d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -120,6 +120,7 @@ jobs: # TODO: remove this after ✅ E2E Tests workflow is available in main e2e-tests: uses: ./.github/workflows/tests-e2e.yml + needs: changes secrets: inherit with: debug: ${{ inputs.debug || false }} From 13b17af26b95f162fabdbdd95ff8a953804c6307 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sun, 8 Jun 2025 20:46:00 +0300 Subject: [PATCH 117/128] refactor playwright e2e tests --- .github/workflows/tests-e2e-playwright.yml | 6 +- .github/workflows/tests-e2e.yml | 4 +- tests/playwright/fixtures/test.ts | 41 +- tests/playwright/helpers/api/api-databases.ts | 50 +- tests/playwright/helpers/api/api-info.ts | 18 - tests/playwright/helpers/api/api-keys.ts | 138 ++- tests/playwright/helpers/api/http-client.ts | 70 +- tests/playwright/helpers/async-helper.ts | 28 - tests/playwright/helpers/common.ts | 310 ----- tests/playwright/helpers/constants.ts | 4 +- tests/playwright/helpers/database.ts | 396 ------- .../helpers/electron/database-scripts.ts | 125 -- tests/playwright/helpers/electron/insights.ts | 51 - tests/playwright/helpers/utils.ts | 40 +- ...uto-discover-redis-enterprise-databases.ts | 38 +- .../pageObjects/base-overview-page.ts | 77 +- tests/playwright/pageObjects/base-page.ts | 38 +- tests/playwright/pageObjects/browser-page.ts | 1036 +++++++++++------ .../pageObjects/components/common/toast.ts | 32 +- .../components/redis-cloud-sign-in-panel.ts | 22 +- .../dialogs/add-rdi-instance-dialog.ts | 19 +- .../dialogs/add-redis-database-dialog.ts | 156 ++- .../dialogs/user-agreement-dialog.ts | 58 +- tests/playwright/pageObjects/index.ts | 1 - .../pageObjects/my-redis-databases-page.ts | 328 ------ .../pageObjects/rdi-instances-list-page.ts | 56 +- tests/playwright/selectors/toast-selectors.js | 9 - tests/playwright/selectors/toast-selectors.ts | 9 + .../selectors/user-agreement-selectors.ts | 4 +- ...mple2.spec.ts => basic-navigation.spec.ts} | 0 tests/playwright/tests/example.spec.ts | 58 - tests/playwright/tests/keys.spec.ts | 101 ++ tests/playwright/types/connections.ts | 16 - tests/playwright/types/databes.ts | 44 + tests/playwright/types/index.ts | 6 + tests/playwright/types/rdi.ts | 8 + 36 files changed, 1322 insertions(+), 2075 deletions(-) delete mode 100644 tests/playwright/helpers/api/api-info.ts delete mode 100644 tests/playwright/helpers/async-helper.ts delete mode 100644 tests/playwright/helpers/common.ts delete mode 100644 tests/playwright/helpers/database.ts delete mode 100755 tests/playwright/helpers/electron/database-scripts.ts delete mode 100755 tests/playwright/helpers/electron/insights.ts delete mode 100755 tests/playwright/pageObjects/my-redis-databases-page.ts delete mode 100644 tests/playwright/selectors/toast-selectors.js create mode 100644 tests/playwright/selectors/toast-selectors.ts rename tests/playwright/tests/{example2.spec.ts => basic-navigation.spec.ts} (100%) delete mode 100644 tests/playwright/tests/example.spec.ts create mode 100644 tests/playwright/tests/keys.spec.ts create mode 100644 tests/playwright/types/rdi.ts diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml index 30dbc20103..972e3cce62 100644 --- a/.github/workflows/tests-e2e-playwright.yml +++ b/.github/workflows/tests-e2e-playwright.yml @@ -1,7 +1,11 @@ name: Playwright E2E Tests on: workflow_call: - + inputs: + debug: + description: SSH Debug + default: false + type: boolean env: E2E_CLOUD_DATABASE_USERNAME: ${{ secrets.E2E_CLOUD_DATABASE_USERNAME }} E2E_CLOUD_DATABASE_PASSWORD: ${{ secrets.E2E_CLOUD_DATABASE_PASSWORD }} diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index 3d1943af3d..4b6fffe8d9 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -53,8 +53,8 @@ jobs: needs: build-docker uses: ./.github/workflows/tests-e2e-playwright.yml secrets: inherit - # with: - # debug: ${{ inputs.debug || false }} + with: + debug: ${{ inputs.debug || false }} # E2E AppImage build-appimage: diff --git a/tests/playwright/fixtures/test.ts b/tests/playwright/fixtures/test.ts index 5321fe5ccc..9a21b2c904 100644 --- a/tests/playwright/fixtures/test.ts +++ b/tests/playwright/fixtures/test.ts @@ -8,19 +8,50 @@ import { } from 'playwright' import log from 'node-color-log' -import { isElectron, electronExecutablePath } from '../helpers/conf' +import { AxiosInstance } from 'axios' +import { apiUrl, isElectron, electronExecutablePath } from '../helpers/conf' +import { generateApiClient } from '../helpers/api/http-client' +import { APIKeyRequests } from '../helpers/api/api-keys' +import { DatabaseAPIRequests } from '../helpers/api/api-databases' +import { UserAgreementDialog } from '../pageObjects' -const commonTest = base.extend<{ forEachTest: void }>({ +type CommonFixtures = { + forEachTest: void + api: { + apiClient: AxiosInstance + keyService: APIKeyRequests + databaseService: DatabaseAPIRequests + } +} + +const commonTest = base.extend({ + api: async ({ page }, use) => { + const windowId = await page.evaluate(() => window.windowId) + + const apiClient = generateApiClient(apiUrl, windowId) + const databaseService = new DatabaseAPIRequests(apiClient) + const keyService = new APIKeyRequests(apiClient, databaseService) + + await use({ apiClient, keyService, databaseService }) + }, forEachTest: [ async ({ page }, use) => { // before each test: if (!isElectron) { await page.goto('/') + } else { + await page.locator('[data-testid="home-tab-databases"]').click() } - await page.waitForSelector('[aria-label="Main navigation"]', { - timeout: 2000, + const userAgreementDialog = new UserAgreementDialog(page) + await userAgreementDialog.acceptLicenseTerms() + + const skipTourElement = page.locator('button', { + hasText: 'Skip tour', }) + if (await skipTourElement.isVisible()) { + skipTourElement.click() + } await use() @@ -46,7 +77,7 @@ const electronTest = commonTest.extend<{ }) // Wait for window startup - await new Promise((resolve) => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 2000)) await use(electronApp) diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts index bac5539266..af233a4dce 100644 --- a/tests/playwright/helpers/api/api-databases.ts +++ b/tests/playwright/helpers/api/api-databases.ts @@ -1,20 +1,13 @@ import { faker } from '@faker-js/faker' import { AxiosInstance } from 'axios' -import { HttpClient } from './http-client' -import { AddNewDatabaseParameters } from '../../types' +import { AddNewDatabaseParameters, DatabaseInstance } from '../../types' import { ResourcePath } from '../constants' -import { asyncFilter, doAsyncStuff } from '../async-helper' export class DatabaseAPIRequests { - private apiClient: AxiosInstance - - constructor(apiUrl: string) { - this.apiClient = new HttpClient(apiUrl).getClient() - } + constructor(private apiClient: AxiosInstance) {} async addNewStandaloneDatabaseApi( databaseParameters: AddNewDatabaseParameters, - xWindowsId: string, isCloud = false, ): Promise { const uniqueId = faker.string.alphanumeric({ length: 10 }) @@ -60,11 +53,6 @@ export class DatabaseAPIRequests { const response = await this.apiClient.post( ResourcePath.Databases, requestBody, - { - headers: { - 'X-Window-Id': xWindowsId, - }, - }, ) if (response.status !== 201) throw new Error( @@ -72,53 +60,35 @@ export class DatabaseAPIRequests { ) } - async getAllDatabases(xWindowsId: string): Promise { - const response = await this.apiClient.get(ResourcePath.Databases, { - headers: { - 'X-Window-Id': xWindowsId, - }, - }) + async getAllDatabases(): Promise { + const response = await this.apiClient.get(ResourcePath.Databases) if (response.status !== 200) throw new Error('Failed to retrieve databases') return response.data } - async getDatabaseIdByName( - databaseName?: string, - xWindowsId: string, - ): Promise { + async getDatabaseIdByName(databaseName?: string): Promise { if (!databaseName) throw new Error('Error: Missing databaseName') - const allDatabases = await this.getAllDatabases(xWindowsId) - const filteredDb = await asyncFilter( - allDatabases, - async (item: databaseParameters) => { - await doAsyncStuff() - return item.name === databaseName - }, - ) + const allDatabases = await this.getAllDatabases() + const foundDb = allDatabases.find((item) => item.name === databaseName) + + if (!foundDb) throw new Error(`Database ${databaseName} not found`) - if (filteredDb.length === 0) - throw new Error(`Database ${databaseName} not found`) - return filteredDb[0].id + return foundDb.id } async deleteStandaloneDatabaseApi( databaseParameters: AddNewDatabaseParameters, - xWindowsId: string, ): Promise { const databaseId = await this.getDatabaseIdByName( databaseParameters.databaseName, - xWindowsId, ) if (!databaseId) throw new Error('Error: Missing databaseId') const requestBody = { ids: [databaseId] } const response = await this.apiClient.delete(ResourcePath.Databases, { data: requestBody, - headers: { - 'X-Window-Id': xWindowsId, - }, }) if (response.status !== 200) throw new Error( diff --git a/tests/playwright/helpers/api/api-info.ts b/tests/playwright/helpers/api/api-info.ts deleted file mode 100644 index cb698f1b19..0000000000 --- a/tests/playwright/helpers/api/api-info.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HttpClient } from './http-client' -import { ResourcePath } from '../constants' - - -/** - * Synchronize features - */ -export async function syncFeaturesApi(apiUrl: string, xWindowsId: string): Promise { - const apiClient = new HttpClient(apiUrl).getClient() - const response = await apiClient.post(ResourcePath.SyncFeatures, {}, - {headers:{ - 'X-Window-Id': xWindowsId - } - }) - if (response.status !== 200) { - throw new Error('Failed to synchronize features') - } -} diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts index 10e7c32827..b1d1809fdd 100755 --- a/tests/playwright/helpers/api/api-keys.ts +++ b/tests/playwright/helpers/api/api-keys.ts @@ -1,88 +1,130 @@ -import {AxiosInstance} from 'axios' -import { HttpClient } from './http-client' +/* eslint-disable max-len */ +import { AxiosInstance } from 'axios' import { DatabaseAPIRequests } from './api-databases' import { AddNewDatabaseParameters, HashKeyParameters, SetKeyParameters, - StreamKeyParameters} from '../../types' + StreamKeyParameters, +} from '../../types' const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' export class APIKeyRequests { + constructor( + private apiClient: AxiosInstance, + private databaseAPIRequests: DatabaseAPIRequests, + ) {} - private databaseAPIRequests: DatabaseAPIRequests - private apiClient: AxiosInstance - - constructor(apiUrl: string) { - this.apiClient = new HttpClient(apiUrl).getClient() - this.databaseAPIRequests = new DatabaseAPIRequests(apiUrl) - } - - async addHashKeyApi(keyParameters: HashKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) + async addHashKeyApi( + keyParameters: HashKeyParameters, + databaseParameters: AddNewDatabaseParameters, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseParameters.databaseName, + ) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - fields: keyParameters.fields.map(fields => ({ + fields: keyParameters.fields.map((fields) => ({ ...fields, field: Buffer.from(fields.field, 'utf-8'), - value: Buffer.from(fields.value, 'utf-8') - })) + value: Buffer.from(fields.value, 'utf-8'), + })), } - const response = await this.apiClient.post(`/databases/${databaseId}/hash?encoding=buffer`, requestBody) - if (response.status !== 201) throw new Error('The creation of new Hash key request failed') + const response = await this.apiClient.post( + `/databases/${databaseId}/hash?encoding=buffer`, + requestBody, + ) + if (response.status !== 201) + throw new Error('The creation of new Hash key request failed') } - async addStreamKeyApi(keyParameters: StreamKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) + async addStreamKeyApi( + keyParameters: StreamKeyParameters, + databaseParameters: AddNewDatabaseParameters, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseParameters.databaseName, + ) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - entries: keyParameters.entries.map(member => ({ + entries: keyParameters.entries.map((member) => ({ ...member, fields: member.fields.map(({ name, value }) => ({ name: Buffer.from(name, 'utf-8'), - value: Buffer.from(value, 'utf-8') - })) - })) + value: Buffer.from(value, 'utf-8'), + })), + })), } - const response = await this.apiClient.post(`/databases/${databaseId}/streams?encoding=buffer`, requestBody) - if (response.status !== 201) throw new Error('The creation of new Stream key request failed') + const response = await this.apiClient.post( + `/databases/${databaseId}/streams?encoding=buffer`, + requestBody, + ) + if (response.status !== 201) + throw new Error('The creation of new Stream key request failed') } - async addSetKeyApi(keyParameters: SetKeyParameters, databaseParameters: AddNewDatabaseParameters): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseParameters.databaseName) + async addSetKeyApi( + keyParameters: SetKeyParameters, + databaseParameters: AddNewDatabaseParameters, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseParameters.databaseName, + ) const requestBody = { keyName: Buffer.from(keyParameters.keyName, 'utf-8'), - members: keyParameters.members.map(member => Buffer.from(member, 'utf-8')) + members: keyParameters.members.map((member) => + Buffer.from(member, 'utf-8'), + ), } - const response = await this.apiClient.post(`/databases/${databaseId}/set?encoding=buffer`, requestBody) - if (response.status !== 201) throw new Error('The creation of new Set key request failed') + const response = await this.apiClient.post( + `/databases/${databaseId}/set?encoding=buffer`, + requestBody, + ) + if (response.status !== 201) + throw new Error('The creation of new Set key request failed') } - async searchKeyByNameApi(keyName: string, databaseName: string, xWindowsId: string): Promise { + async searchKeyByNameApi( + keyName: string, + databaseName: string, + ): Promise { const requestBody = { cursor: '0', - match: keyName + match: keyName, } - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName, xWindowsId) - const response = await this.apiClient.post(bufferPathMask.replace('databaseId', databaseId), requestBody, { - headers:{ - 'X-Window-Id': xWindowsId - } - }) - if (response.status !== 200) throw new Error('Getting key request failed') + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseName, + ) + const response = await this.apiClient.post( + bufferPathMask.replace('databaseId', databaseId), + requestBody, + ) + if (response.status !== 200) + throw new Error('Getting key request failed') return response.data[0].keys } - async deleteKeyByNameApi(keyName: string, databaseName: string, xWindowsId: string): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName, xWindowsId) - const doesKeyExist = await this.searchKeyByNameApi(keyName, databaseName, xWindowsId) + async deleteKeyByNameApi( + keyName: string, + databaseName: string, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseName, + ) + const doesKeyExist = await this.searchKeyByNameApi( + keyName, + databaseName, + ) if (doesKeyExist.length > 0) { const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } - const response = await this.apiClient.delete(bufferPathMask.replace('databaseId', databaseId), { data: requestBody, - headers:{ - 'X-Window-Id': xWindowsId - }}) - if (response.status !== 200) throw new Error('The deletion of the key request failed') + const response = await this.apiClient.delete( + bufferPathMask.replace('databaseId', databaseId), + { + data: requestBody, + }, + ) + if (response.status !== 200) + throw new Error('The deletion of the key request failed') } } } diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts index f035b073ed..24d352e454 100644 --- a/tests/playwright/helpers/api/http-client.ts +++ b/tests/playwright/helpers/api/http-client.ts @@ -1,50 +1,34 @@ import axios, { AxiosInstance } from 'axios' import https from 'https' -interface CustomAxiosInstance extends AxiosInstance { - setHeaders: (headers: Record) => void -} - -export class HttpClient { - private apiUrl: string - - private apiClient: CustomAxiosInstance +export function generateApiClient(apiUrl: string, windowId?: string): AxiosInstance { + const apiClient = axios.create({ + baseURL: apiUrl, + headers: { + 'X-Window-Id': windowId, + }, + httpsAgent: new https.Agent({ + rejectUnauthorized: false, // Allows self-signed/invalid SSL certs + }), + }) - constructor(apiUrl: string) { - this.apiUrl = apiUrl - this.apiClient = axios.create({ - baseURL: this.apiUrl, - headers: {}, - httpsAgent: new https.Agent({ - rejectUnauthorized: false, // Allows self-signed/invalid SSL certs - }), - }) as CustomAxiosInstance - - // Attach setHeaders method to allow setting headers dynamically - this.apiClient.setHeaders = (headers: Record) => { - Object.assign(this.apiClient.defaults.headers.common, headers) - } - - // Enable logging if DEBUG is set - if (process.env.DEBUG) { - this.apiClient.interceptors.request.use((request) => { - console.log('Starting Request', request) - return request - }) - this.apiClient.interceptors.response.use( - (response) => { - console.log('Response:', response) - return response - }, - (error) => { - console.error('Error Response:', error.response) - return Promise.reject(error) - }, - ) - } + // Enable logging if DEBUG is set + if (process.env.DEBUG) { + this.apiClient.interceptors.request.use((request) => { + console.log('Starting Request', request) + return request + }) + this.apiClient.interceptors.response.use( + (response) => { + console.log('Response:', response) + return response + }, + (error) => { + console.error('Error Response:', error.response) + return Promise.reject(error) + }, + ) } - getClient(): CustomAxiosInstance { - return this.apiClient - } + return apiClient } diff --git a/tests/playwright/helpers/async-helper.ts b/tests/playwright/helpers/async-helper.ts deleted file mode 100644 index 062ef23c6d..0000000000 --- a/tests/playwright/helpers/async-helper.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Helper function to work with arr.filter() method with async functions - * @param array The array - * @param callback The callback function need to be processed - */ -async function asyncFilter(array: string[], callback: (item: any) => Promise): Promise { - const fail = Symbol(); - return (await Promise.all(array.map(async item => (await callback(item)) ? item : fail))).filter(i => i !== fail); -} - -/** - * Helper function to work with arr.find() method with async functions - * @param array The array - * @param asyncCallback The callback function need to be processed - */ -async function asyncFind(array: string[], asyncCallback: (item: any) => Promise): Promise { - const index = (await Promise.all(array.map(asyncCallback))).findIndex(result => result); - return array[index]; -} - -/** - * Helper function for waiting until promise be resolved - */ -function doAsyncStuff(): Promise { - return Promise.resolve(); -} - -export { asyncFilter, asyncFind, doAsyncStuff }; \ No newline at end of file diff --git a/tests/playwright/helpers/common.ts b/tests/playwright/helpers/common.ts deleted file mode 100644 index 17436e100b..0000000000 --- a/tests/playwright/helpers/common.ts +++ /dev/null @@ -1,310 +0,0 @@ -// import { ClientFunction, RequestMock, t } from 'testcafe' - -// import * as path from 'path' -// import * as fs from 'fs' -// import * as fsp from 'fs/promises' -import { faker } from '@faker-js/faker' - - -// const archiver = require('archiver') -// -// -// -declare global { - interface Window { - windowId?: string - } -} -// -// const settingsApiUrl = `${apiUrl}/settings` -// process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' // lgtm[js/disabling-certificate-validation] -// const mockedSettingsResponse = { -// 'theme': null, -// 'dateFormat': null, -// 'timezone': null, -// 'scanThreshold': 10000, -// 'batchSize': 5, -// 'agreements': null -// } -// -export class Common { -// static mockSettingsResponse(): RequestMock { -// return RequestMock() -// .onRequestTo(settingsApiUrl) -// .respond(mockedSettingsResponse, 200, { -// 'Access-Control-Allow-Origin': '*', -// 'Access-Control-Allow-Credentials': 'true', -// 'Access-Control-Allow-Headers': 'x-window-id' -// }) -// } -// -// static async waitForElementNotVisible(elm: Selector): Promise { -// await t.expect(elm.exists).notOk({ timeout: 10000 }) -// } -// -// /** -// * Create array of keys -// * @param length The amount of array elements -// */ -// static createArrayWithKeys(length: number): string[] { -// return Array.from({ length }, (_, i) => `key${i}`) -// } -// -// /** -// * Create array of keys and values -// * @param length The amount of array elements -// */ -// static async createArrayWithKeyValue(length: number): Promise { -// const arr: string[] = [] -// for (let i = 1; i <= length * 2; i++) { -// arr[i] = `${chance.word({ length: 10 })}-key${i}` -// arr[i + 1] = `${chance.word({ length: 10 })}-value${i}` -// i++ -// } -// return arr -// } -// -// /** -// * Create array of keys and values -// * @param length The amount of array elements -// */ -// static async createArrayWithKeyValueAndDelimiter(length: number): Promise { -// const keyNameArray: string[] = [] -// for (let i = 1; i <= length; i++) { -// const key = `"key${i}:test${i}"` -// const value = `"value${this.generateSentence(i * 2)}"` -// keyNameArray.push(key, value) -// } -// return keyNameArray -// } -// -// /** -// * Create array of keys and values -// * @param length The amount of array elements -// */ -// static async createArrayWithKeyAndDelimiter(length: number): Promise { -// const keyNameArray: string[] = [] -// for (let i = 1; i <= length; i++) { -// const key = `"key${i}:test${i}"` -// keyNameArray.push(key) -// } -// return keyNameArray -// } -// -// /** -// * Create array of keys and values for using in OSS Cluster -// * @param length The amount of array elements -// */ -// static async createArrayWithKeyValueForOSSCluster(length: number): Promise { -// const arr: string[] = [] -// for (let i = 1; i <= length * 2; i++) { -// arr[i] = `{user1}:${chance.word({ length: 10 })}-key${i}` -// arr[i + 1] = `${chance.word({ length: 10 })}-value${i}` -// i++ -// } -// return arr -// } -// -// /** -// * Create array of keys and values with edittable counter value -// * @param length The amount of array elements -// * @param keyName The name of the key -// */ -// static async createArrayWithKeyValueAndKeyname(length: number, keyName: string): Promise { -// const keyNameArray: string[] = [] -// for (let i = 1; i <= length; i++) { -// const key = `${keyName}${i}` -// const value = `value${i}` -// keyNameArray.push(key, value) -// } -// return keyNameArray -// } -// -// /** -// * Create array of pairs [key, value] -// * @param length The amount of array elements -// */ -// static createArrayPairsWithKeyValue(length: number): [string, number][] { -// return Array.from({ length }, (_, i) => [`key${i}`, i]) -// } -// -// /** -// * Create array of numbers -// * @param length The amount of array elements -// */ -// static async createArray(length: number): Promise { -// const arr: string[] = [] -// for (let i = 1; i <= length; i++) { -// arr[i] = `${i}` -// } -// return arr -// } -// -// /** -// * Get background colour of element -// * @param element The selector of the element -// */ -// static async getBackgroundColour(element: Selector): Promise { -// return element.getStyleProperty('background-color') -// } -// - /** - * Generate word by number of symbols - * @param number The number of symbols - */ - static generateWord(number: number): string { - return faker.word.sample({ length: number }) - } - - /** - * Generate sentence by number of words - * @param number The number of words - */ - static generateSentence(number: number): string { - return faker.lorem.sentence( number) - } - - /** - * Generate sentence by number of characters - * @param number The number of characters - */ - static generateAlphanumeric(number: number): string { - return faker.string.alphanumeric(number) - } - - - // /** - // * Return api endpoint with disabled certificate validation - // */ - // static function getEndpoint(): string { - // return apiUrl - // } - // - // /** - // * Return windowId - // */ - // static function getWindowId(): Promise { - // return t.eval(() => window.windowId) - // } - - // /** - // * Check opened URL - // * @param expectedUrl Expected link that is compared with actual - // */ - // static async checkURL(expectedUrl: string): Promise { - // const getPageUrl = await this.getPageUrl() - // await t.expect(getPageUrl).eql(expectedUrl, 'Opened URL is not correct') - // } - - // /** - // * Check opened URL contains text - // * @param expectedText Expected link that is compared with actual - // */ - // static async checkURLContainsText(expectedText: string): Promise { - // const getPageUrl = await this.getPageUrl() - // await t.expect(getPageUrl).contains(expectedText, `Opened URL not contains text ${expectedText}`) - // } - // - // /** - // * Replace spaces and line breaks - // * @param text text to be replaced - // */ - // static async removeEmptySpacesAndBreak(text: string): Promise { - // return text - // .replace(/ /g, '') - // .replace(/\n/g, '') - // } - // - // /** - // * Get current page url - // */ - // static async getPageUrl(): Promise { - // return (ClientFunction(() => window.location.href))() - // } - // - // /** - // * generate url base on params to create DB - // * @param params params for creating DB - // */ - // static generateUrlTParams(params: Record): string { - // return new URLSearchParams(params).toString() - // } - // - // /** - // * Get json property value by property name and path - // * @param expectedText Expected link that is compared with actual - // */ - // static async getJsonPropertyValue(property: string, path: string): Promise { - // const parsedJson = JSON.parse(fs.readFileSync(path, 'utf-8')) - // return parsedJson[property] - // } - // - // /** - // * Create Zip archive from folder - // * @param folderPath Path to folder to archive - // * @param zipName Zip archive name - // */ - // static async createZipFromFolder(folderPath: string, zipName: string): Promise { - // const sourceDir = path.join(__dirname, folderPath) - // const zipFilePath = path.join(__dirname, zipName) - // const output = fs.createWriteStream(zipFilePath) - // const archive = archiver('zip', { zlib: { level: 9 } }) - // - // // Add the contents of the directory to the zip archive - // archive.directory(sourceDir, false) - // // Finalize the archive and write it to disk - // await archive.finalize() - // archive.pipe(output) - // } - // - // /** - // * Delete file from folder - // * @param filePath Path to file - // */ - // static async deleteFileFromFolder(filePath: string): Promise { - // fs.unlinkSync(path.join(__dirname, filePath)) - // } - // - // /** - // * Delete file from folder if exists - // * @param filePath Path to file - // */ - // static async deleteFileFromFolderIfExists(filePath: string): Promise { - // if (fs.existsSync(filePath)) { - // fs.unlinkSync(filePath) - // } - // } - // - // /** - // * Delete folder - // * @param filePath Path to file - // */ - // static async deleteFolderIfExists(filePath: string): Promise { - // try { - // await fsp.rm(filePath, { recursive: true, force: true }) - // console.log(`Directory Deleted: ${filePath}`) - // } catch (error) { - // console.error(`Failed to delete directory: ${filePath}`, error) - // } - // } - // - // /** - // * Read file from folder - // * @param filePath Path to file - // */ - // static async readFileFromFolder(filePath: string): Promise { - // return fs.readFileSync(filePath, 'utf8') - // } - // - // /** - // * Get current machine platform - // */ - // static getPlatform(): { isMac: boolean, isLinux: boolean } { - // return { - // isMac: process.platform === 'darwin', - // isLinux: process.platform === 'linux' - // } - } - - - diff --git a/tests/playwright/helpers/constants.ts b/tests/playwright/helpers/constants.ts index 2d88f5c452..af4dc6199d 100644 --- a/tests/playwright/helpers/constants.ts +++ b/tests/playwright/helpers/constants.ts @@ -23,7 +23,7 @@ export const COMMANDS_TO_CREATE_KEY = Object.freeze({ [KeyTypesTexts.TimeSeries]: (key: string) => `TS.CREATE ${key}` }) -export enum rte { +export enum RTE { none = 'none', standalone = 'standalone', sentinel = 'sentinel', @@ -32,7 +32,7 @@ export enum rte { reCloud = 're-cloud' } -export enum env { +export enum ENV { web = 'web', desktop = 'desktop' } diff --git a/tests/playwright/helpers/database.ts b/tests/playwright/helpers/database.ts deleted file mode 100644 index 571b2bd921..0000000000 --- a/tests/playwright/helpers/database.ts +++ /dev/null @@ -1,396 +0,0 @@ -import {expect, Page} from '@playwright/test' -import { DatabaseAPIRequests } from './api/api-databases' -import { RedisOverviewPage } from './constants' -import { updateControlNumber } from './electron/insights' -import { - AddNewDatabaseParameters, - SentinelParameters, - OSSClusterParameters -} from '../types' -import { UserAgreementDialog } from '../pageObjects/dialogs/user-agreement-dialog' -import { RdiInstancesListPage } from '../pageObjects/rdi-instances-list-page' -// import { DiscoverMasterGroupsPage } from '../pageObjects/sentinel/discovered-sentinel-master-groups-page' -import { - MyRedisDatabasePage, - BrowserPage, - AutoDiscoverREDatabases, - AddRedisDatabaseDialog, - BasePage -} from '../pageObjects' - - - -export class DatabaseHelper extends BasePage{ - private myRedisDatabasePage: MyRedisDatabasePage - private addRedisDataBaseDialog: AddRedisDatabaseDialog - - -// const discoverMasterGroupsPage = new DiscoverMasterGroupsPage() - private autoDiscoverREDatabases: AutoDiscoverREDatabases - private browserPage: BrowserPage - private userAgreementDialog: UserAgreementDialog - private databaseAPIRequests: DatabaseAPIRequests - private rdiInstancesListPage: RdiInstancesListPage - - constructor(page: Page, apiUrl: string) { - super(page) - this.addRedisDataBaseDialog = new AddRedisDatabaseDialog(page) - this.autoDiscoverREDatabases = new AutoDiscoverREDatabases(page) - this.browserPage = new BrowserPage(page) - this.userAgreementDialog = new UserAgreementDialog(page) - this.rdiInstancesListPage = new RdiInstancesListPage(page) - this.myRedisDatabasePage = new MyRedisDatabasePage(page, apiUrl) - this.databaseAPIRequests = new DatabaseAPIRequests(apiUrl) - - - } - /** - * Add a new database manually using host and port - * @param databaseParameters The database parameters - */ - async addNewStandaloneDatabase( - databaseParameters: AddNewDatabaseParameters - ): Promise { - // Fill the add database form - - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase( - databaseParameters - ) - // Click for saving - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() - // Wait for database to exist - await expect( - this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) - ).toBeVisible({ timeout: 10000 }) - // Close message - await this.myRedisDatabasePage.toast.toastCloseButton.click() - } - - // /** - // * Add a new database via autodiscover using Sentinel option - // * @param databaseParameters The Sentinel parameters: host, port and sentinel password - // */ - // async discoverSentinelDatabase( - // databaseParameters: SentinelParameters - // ): Promise { - // // Fill sentinel parameters to auto-discover Master Groups - // await this.myRedisDatabasePage.addRedisDatabaseDialog.discoverSentinelDatabases( - // databaseParameters - // ) - // // Click for autodiscover - // await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() - // await expect(discoverMasterGroupsPage.addPrimaryGroupButton).toBeVisible({ - // timeout: 10000 - // }) - // // Select Master Groups and Add to Redis Insight - // await discoverMasterGroupsPage.addMasterGroups() - // await autoDiscoverREDatabases.viewDatabasesButton.click() - // } - - /** - * Add a new database from RE Cluster via auto-discover flow - * @param databaseParameters The database parameters - */ - async addNewREClusterDatabase( - databaseParameters: AddNewDatabaseParameters - ): Promise { - // Fill the add database form - await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverREClusterDatabase( - databaseParameters - ) - // Click on submit button - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() - // Wait for database to exist in the list of Autodiscover databases and select it - await expect( - this.autoDiscoverREDatabases.databaseName.getByText(databaseParameters.databaseName ?? '', { exact: true }) - ).toBeVisible({ timeout: 10000 }) - await this.autoDiscoverREDatabases.search.fill(databaseParameters.databaseName ?? '') - await this.autoDiscoverREDatabases.databaseCheckbox.click() - // Click Add selected databases button - await this.autoDiscoverREDatabases.addSelectedDatabases.click() - await this.autoDiscoverREDatabases.viewDatabasesButton.click() - } - - /** - * Add a new database from OSS Cluster via auto-discover flow - * @param databaseParameters The database parameters - */ - async addOSSClusterDatabase( - databaseParameters: OSSClusterParameters - ): Promise { - // Enter required parameters for OSS Cluster - await this.myRedisDatabasePage.addRedisDatabaseDialog.addOssClusterDatabase( - databaseParameters - ) - // Click for saving - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() - // Check for info message that DB was added - await expect(this.myRedisDatabasePage.toast.toastHeader).toBeVisible({ timeout: 10000 }) - // Wait for database to exist - await expect( - this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.ossClusterDatabaseName, { exact: true }) - ).toBeVisible({ timeout: 10000 }) - } - - /** - * Add a new database from Redis Cloud via auto-discover flow - * @param cloudAPIAccessKey The Cloud API Access Key - * @param cloudAPISecretKey The Cloud API Secret Key - */ - async autodiscoverRECloudDatabase( - cloudAPIAccessKey: string, - cloudAPISecretKey: string - ): Promise { - // Fill the add database form and Submit - await this.myRedisDatabasePage.addRedisDatabaseDialog.addAutodiscoverRECloudDatabase( - cloudAPIAccessKey, - cloudAPISecretKey - ) - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() - await expect( - autoDiscoverREDatabases.title.getByText('Redis Cloud Subscriptions', { exact: true }) - ).toBeVisible({ timeout: 120000 }) - // Select subscriptions - await this.myRedisDatabasePage.addRedisDatabaseDialog.selectAllCheckbox.click() - await this.myRedisDatabasePage.addRedisDatabaseDialog.showDatabasesButton.click() - // Select databases for adding - const databaseName = await autoDiscoverREDatabases.getDatabaseName() - await autoDiscoverREDatabases.databaseCheckbox.click() - await autoDiscoverREDatabases.addSelectedDatabases.click() - // Wait for database to exist in the redis databases list - await expect( - autoDiscoverREDatabases.title.getByText('Redis Enterprise Databases Added', { exact: true }) - ).toBeVisible({ timeout: 20000 }) - await autoDiscoverREDatabases.viewDatabasesButton.click() - // uncomment when fixed db will be added to cloud subscription - // await expect(this.myRedisDatabasePage.dbNameList.getByText(databaseName, { exact: true })).toBeVisible({ timeout: 10000 }); - return databaseName - } - - /** - * Accept License terms and add database - * @param databaseParameters The database parameters - * @param databaseName The database name - */ - async acceptLicenseTermsAndAddDatabase( - databaseParameters: AddNewDatabaseParameters - ): Promise { - await this.acceptLicenseTerms() - await this.addNewStandaloneDatabase(databaseParameters) - // Connect to DB - await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) - } - - /** - * Accept License terms and add database using api - * @param databaseParameters The database parameters - * @param databaseName The database name - */ - async acceptLicenseTermsAndAddDatabaseApi( - databaseParameters: AddNewDatabaseParameters, - page: Page, - apiUrl: string, - ): Promise { - - await this.acceptLicenseTerms(page,apiUrl) - await this.databaseAPIRequests.addNewStandaloneDatabaseApi(databaseParameters, await this.getWindowId()) - // Reload Page to see the new added database through api - await this.myRedisDatabasePage.reloadPage() - // Connect to DB - await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName!) - - if(await this.isVisible("Skip tour")){ - await this.page.locator("Skip tour").click() - } - - } - - /** - * Accept License terms and add OSS cluster database - * @param databaseParameters The database parameters - * @param databaseName The database name - */ - async acceptLicenseTermsAndAddOSSClusterDatabase( - databaseParameters: OSSClusterParameters - ): Promise { - await this.acceptLicenseTerms() - await this.addOSSClusterDatabase(databaseParameters) - // Connect to DB - await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.ossClusterDatabaseName!) - } - - /** - * Accept License terms and add Sentinel database using api - * @param databaseParameters The database parameters - */ - async acceptLicenseTermsAndAddSentinelDatabaseApi( - databaseParameters: SentinelParameters - ): Promise { - await this.acceptLicenseTerms() - await databaseAPIRequests.discoverSentinelDatabaseApi(databaseParameters) - // Reload Page to see the database added through api - await this.myRedisDatabasePage.reloadPage() - // Connect to DB - await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.masters![1].alias ?? '') - } - - /** - * Accept License terms and add RE Cluster database - * @param databaseParameters The database parameters - */ - async acceptLicenseTermsAndAddREClusterDatabase( - databaseParameters: AddNewDatabaseParameters - ): Promise { - await this.acceptLicenseTerms() - await this.addNewREClusterDatabase(databaseParameters) - // Connect to DB - await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') - } - - /** - * Accept License terms and add RE Cloud database - * @param databaseParameters The database parameters - */ - async acceptLicenseTermsAndAddRECloudDatabase( - databaseParameters: AddNewDatabaseParameters - ): Promise { - const searchTimeout = 60 * 1000 // 60 sec to wait database appearing - const dbSelector = this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) - const startTime = Date.now() - - await this.acceptLicenseTerms() - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase(databaseParameters) - // Click for saving - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() - await this.myRedisDatabasePage.page.waitForTimeout(3000) - // Reload page until db appears - while (!(await dbSelector.isVisible()) && Date.now() - startTime < searchTimeout) { - await this.myRedisDatabasePage.reloadPage() - } - await expect( - this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) - ).toBeVisible({ timeout: 5000 }) - await this.myRedisDatabasePage.clickOnDBByName(databaseParameters.databaseName ?? '') - await expect(browserPage.keysSummary).toBeVisible({ timeout: 15000 }) - } - - /** - * Add RE Cloud database - * @param databaseParameters The database parameters - */ - async addRECloudDatabase( - databaseParameters: AddNewDatabaseParameters - ): Promise { - const searchTimeout = 60 * 1000 // 60 sec to wait database appearing - const dbSelector = this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) - const startTime = Date.now() - - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDataBase(databaseParameters) - // Click for saving - await this.myRedisDatabasePage.addRedisDatabaseDialog.addRedisDatabaseButton.click() - await this.myRedisDatabasePage.page.waitForTimeout(3000) - // Reload page until db appears - while (!(await dbSelector.isVisible()) && Date.now() - startTime < searchTimeout) { - await this.myRedisDatabasePage.reloadPage() - } - await expect( - this.myRedisDatabasePage.dbNameList.getByText(databaseParameters.databaseName ?? '', { exact: true }) - ).toBeVisible({ timeout: 5000 }) - } - - // Accept License terms - async acceptLicenseTerms(page: Page, apiUrl: string ): Promise { - // await this.myRedisDatabasePage.page.viewportSize(); - const winId = await this.getWindowId() - await this.userAgreementDialog.acceptLicenseTerms() - await updateControlNumber(48.2, apiUrl, winId) - // Open default databases list tab if RDI opened - if (await this.rdiInstancesListPage.elementExistsLocator(this.rdiInstancesListPage.addRdiInstanceButton)) { - await this.myRedisDatabasePage.setActivePage(RedisOverviewPage.DataBase) - } - // TODO delete after releasing chatbot - if (await this.myRedisDatabasePage.addRedisDatabaseDialog.aiChatMessage.isVisible()) { - await this.myRedisDatabasePage.addRedisDatabaseDialog.aiCloseMessage.click() - } - } - - // Accept License terms and connect to the RedisStack database - // async acceptLicenseAndConnectToRedisStack(): Promise { - // await this.acceptLicenseTerms() - // // Connect to DB - // await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() - // await this.myRedisDatabasePage.addRedisDatabaseDialog.connectToRedisStackButton.click() - // } - - /** - * Delete database - * @param databaseName The database name - */ - // async deleteDatabase(databaseName: string): Promise { - // await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() - // if ( - // await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() - // ) { - // await this.deleteDatabaseByNameApi(databaseName) - // } - // } - - /** - * Delete database with custom name - * @param databaseName The database name - // */ - // async deleteCustomDatabase(databaseName: string): Promise { - // await this.myRedisDatabasePage.NavigationPanel.myRedisDBButton.click() - // if ( - // await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() - // ) { - // await this.myRedisDatabasePage.deleteDatabaseByName(databaseName) - // } - // } - - // /** - // * Accept License terms and add database or connect to the Redis stask database - // * @param databaseParameters The database parameters - // * @param databaseName The database name - // */ - // async acceptTermsAddDatabaseOrConnectToRedisStack( - // databaseParameters: AddNewDatabaseParameters - // ): Promise { - // if ( - // await this.myRedisDatabasePage.addRedisDatabaseDialog.addDatabaseButton.isVisible() - // ) { - // await this.acceptLicenseTermsAndAddDatabase(databaseParameters) - // } else { - // await this.acceptLicenseAndConnectToRedisStack() - // } - // } - - /** - * Click on the edit database button by name - * @param databaseName The name of the database - */ - async clickOnEditDatabaseByName(databaseName: string): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) - const databaseEditBtn = this.myRedisDatabasePage.page.locator( - `[data-testid=edit-instance-${databaseId}]` - ) - - await expect(databaseEditBtn).toBeVisible() - await databaseEditBtn.click() - } - - /** - * Delete database button by name - * @param databaseName The name of the database - */ - async deleteDatabaseByNameApi(databaseName: string): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) - const databaseDeleteBtn = this.myRedisDatabasePage.page.locator( - `[data-testid=delete-instance-${databaseId}-icon]` - ) - - await expect(databaseDeleteBtn).toBeVisible() - await databaseDeleteBtn.click() - await this.myRedisDatabasePage.confirmDeleteButton.click() - } -} diff --git a/tests/playwright/helpers/electron/database-scripts.ts b/tests/playwright/helpers/electron/database-scripts.ts deleted file mode 100755 index ae2e7e8cf1..0000000000 --- a/tests/playwright/helpers/electron/database-scripts.ts +++ /dev/null @@ -1,125 +0,0 @@ -import * as sqlite3 from 'sqlite3' -import {promisify} from 'util' -import {workingDirectory} from '../conf' -import {createTimeout} from '../utils' - -const dbPath = `${workingDirectory}/redisinsight.db` - -export class DatabaseScripts { - /** - * Update table column value into local DB for a specific row - * @param dbTableParameters The sqlite database table parameters - */ - static async updateColumnValueInDBTable(dbTableParameters: DbTableParameters): Promise { - const db = new sqlite3.Database(dbPath) - try { - const runAsync = (query: string, p: (string | number | undefined)[]) => promisify(db.run.bind(db)) // convert db.run to a Promise-based function - const query = `UPDATE ${dbTableParameters.tableName} - SET ${dbTableParameters.columnName} = ? - WHERE ${dbTableParameters.conditionWhereColumnName} = ?` - await runAsync(query, [dbTableParameters.rowValue, dbTableParameters.conditionWhereColumnValue]) - } catch (err) { - console.log(`Error during changing ${dbTableParameters.columnName} column value: ${err}`) - // throw new Error( - // `Error during changing ${dbTableParameters.columnName} column value: ${err}`, - // ) - } finally { - console.log('Close DB') - db.close() - } - - } - - /** - * Get Column value from table in local Database - * @param dbTableParameters The sqlite database table parameters - */ - static async getColumnValueFromTableInDB(dbTableParameters: DbTableParameters): Promise { - // Open the database in read/write mode and fail early if it cannot be opened. - const db = await new Promise((resolve, reject) => { - const database = new sqlite3.Database( - dbPath, - sqlite3.OPEN_READWRITE, - (err: Error | null) => { - if (err) { - reject(new Error(`Error opening DB at path ${dbPath}: ${err.message}`)) - } else { - resolve(database) - } - } - ) - }) - - const query = `SELECT ${dbTableParameters.columnName} - FROM ${dbTableParameters.tableName} - WHERE ${dbTableParameters.conditionWhereColumnName} = ?` - try { - const getAsync = (query: string, p: (string | number | undefined)[]) => promisify(db.get.bind(db)) - const row = await Promise.race([ - getAsync(query, [dbTableParameters.conditionWhereColumnValue]), - createTimeout('Query timed out after 10 seconds',10000) - ]) - if (!row) { - throw new Error(`No row found for column ${dbTableParameters.columnName}`) - } - return row[dbTableParameters.columnName!] - } catch (err: any) { - throw new Error(`Error during getting ${dbTableParameters.columnName} column value: ${err.message}`) - } finally { - db.close() - } - } - - /** - * Delete all rows from table in local DB - * @param dbTableParameters The sqlite database table parameters - */ - static async deleteRowsFromTableInDB(dbTableParameters: DbTableParameters): Promise { - const db = await new Promise((resolve, reject) => { - const database = new sqlite3.Database( - dbPath, - sqlite3.OPEN_READWRITE, - (err: Error | null) => { - if (err) { - console.log(`Error during deleteRowsFromTableInDB: ${err}`) - reject(new Error(`Error opening DB at path ${dbPath}: ${err.message}`)) - } else { - resolve(database) - } - } - ) - }) - - const query = `DELETE - FROM ${dbTableParameters.tableName}` - - try { - const runAsync = promisify(db.run.bind(db)) - await Promise.race([ - runAsync(query), - createTimeout('DELETE operation timed out after 10 seconds', 10000) - ]) - } catch (err: any) { - throw new Error(`Error during ${dbTableParameters.tableName} table rows deletion: ${err.message}`) - } finally { - db.close() - } - } - -} - -/** - * Add new database parameters - * @param tableName The name of table in DB - * @param columnName The name of column in table - * @param rowValue Value to update in table - * @param conditionWhereColumnName The name of the column to search - * @param conditionWhereColumnValue The value to match in the column - */ -export type DbTableParameters = { - tableName: string, - columnName?: string, - rowValue?: string | number, - conditionWhereColumnName?: string, - conditionWhereColumnValue?: string -} diff --git a/tests/playwright/helpers/electron/insights.ts b/tests/playwright/helpers/electron/insights.ts deleted file mode 100755 index e6fc271461..0000000000 --- a/tests/playwright/helpers/electron/insights.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as fs from 'fs-extra' -import * as path from 'path' -import { syncFeaturesApi } from '../api/api-info' -import { DatabaseScripts, DbTableParameters } from './database-scripts' - -const dbTableParams: DbTableParameters = { - tableName: 'features_config', - columnName: 'controlNumber', - conditionWhereColumnName: 'id', - conditionWhereColumnValue: '1' -} - -/** - * Update features-config file for static server - * @param filePath Path to feature config json - */ -export async function modifyFeaturesConfigJson(filePath: string): Promise { - const configFileName = 'features-config.json' - const remoteConfigPath = process.env.REMOTE_FOLDER_PATH || './remote' - const targetFilePath = path.join(remoteConfigPath, configFileName) - - try { - fs.ensureFileSync(targetFilePath) - fs.writeFileSync(targetFilePath, fs.readFileSync(filePath)) - } catch (err: any) { - throw new Error(`Error updating remote config file: ${err.message}`) - } -} - -/** - * Update Control Number of current user and sync - * @param controlNumber Control number to update - * @param page Playwright page instance - */ -export async function updateControlNumber(controlNumber: number, apiUrl: string, xWindowId:string): Promise { - await syncFeaturesApi(apiUrl, xWindowId) - // await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }) - await syncFeaturesApi(apiUrl, xWindowId) - -} - -/** - * Refresh test data for features sync - */ -export async function refreshFeaturesTestData(apiUrl: string, xWindowId:string): Promise { - const defaultConfigPath = path.join('.', 'test-data', 'features-configs', 'insights-default-remote.json') - - await modifyFeaturesConfigJson(defaultConfigPath) - await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams) - await syncFeaturesApi(apiUrl, xWindowId) -} diff --git a/tests/playwright/helpers/utils.ts b/tests/playwright/helpers/utils.ts index 053db8ef19..4012dc8cfa 100644 --- a/tests/playwright/helpers/utils.ts +++ b/tests/playwright/helpers/utils.ts @@ -1,6 +1,34 @@ -export const createTimeout = (errorMessage: string, timeout: number): Promise => - new Promise((_, reject) => { - setTimeout(() => { - reject(new Error(errorMessage)); - }, timeout); - }); +import { expect, Page } from '@playwright/test' + +import { DatabaseAPIRequests } from './api/api-databases' +import { ossStandaloneConfig } from './conf' + +export async function addStandaloneInstanceAndNavigateToIt( + page: Page, + databaseService: DatabaseAPIRequests, +): Promise<() => Promise> { + // Add a new standalone database + databaseService.addNewStandaloneDatabaseApi(ossStandaloneConfig) + + page.reload() + + return async function cleanup() { + try { + await databaseService.deleteStandaloneDatabaseApi( + ossStandaloneConfig, + ) + } catch (error) { + console.warn('Error during cleanup:', error) + } + } +} + +export async function navigateToStandaloneInstance(page: Page): Promise { + // Click on the added database + const dbItems = page.locator('[data-testid^="instance-name"]') + const db = dbItems.filter({ + hasText: ossStandaloneConfig.databaseName.trim(), + }) + await expect(db).toBeVisible({ timeout: 5000 }) + await db.first().click() +} diff --git a/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts b/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts index 30a6887db9..9acbeb34c0 100755 --- a/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts +++ b/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts @@ -1,30 +1,36 @@ -import { Page, Locator } from '@playwright/test'; -import { BasePage } from './base-page'; +import { Page, Locator } from '@playwright/test' +import { BasePage } from './base-page' export class AutoDiscoverREDatabases extends BasePage { - readonly page: Page; // BUTTONS - readonly addSelectedDatabases: Locator; - readonly databaseCheckbox: Locator; - readonly search: Locator; - readonly viewDatabasesButton: Locator; + readonly addSelectedDatabases: Locator + + readonly databaseCheckbox: Locator + + readonly search: Locator + + readonly viewDatabasesButton: Locator + // TEXT INPUTS - readonly title: Locator; - readonly databaseName: Locator; + readonly title: Locator + + readonly databaseName: Locator constructor(page: Page) { super(page) this.page = page - this.addSelectedDatabases = page.getByTestId('btn-add-databases'); - this.databaseCheckbox = page.locator('[data-test-subj^="checkboxSelectRow"]'); - this.search = page.getByTestId('search'); - this.viewDatabasesButton = page.getByTestId('btn-view-databases'); - this.title = page.getByTestId('title'); - this.databaseName = page.locator('[data-testid^="db_name_"]'); + this.addSelectedDatabases = page.getByTestId('btn-add-databases') + this.databaseCheckbox = page.locator( + '[data-test-subj^="checkboxSelectRow"]', + ) + this.search = page.getByTestId('search') + this.viewDatabasesButton = page.getByTestId('btn-view-databases') + this.title = page.getByTestId('title') + this.databaseName = page.locator('[data-testid^="db_name_"]') } // Get databases name async getDatabaseName(): Promise { - return this.databaseName.textContent(); + return this.databaseName.textContent() } } diff --git a/tests/playwright/pageObjects/base-overview-page.ts b/tests/playwright/pageObjects/base-overview-page.ts index 94f36762a2..3fb7401355 100644 --- a/tests/playwright/pageObjects/base-overview-page.ts +++ b/tests/playwright/pageObjects/base-overview-page.ts @@ -1,65 +1,68 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-restricted-syntax */ import { expect, Locator, Page } from '@playwright/test' import { Toast } from './components/common/toast' import { BasePage } from './base-page' import { RedisOverviewPage } from '../helpers/constants' -import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { DatabasesForImport } from '../types' - export class BaseOverviewPage extends BasePage { // Component instance used in methods toast: Toast // BUTTONS & ACTION SELECTORS - private readonly deleteRowButton: Locator + readonly deleteRowButton: Locator - private readonly confirmDeleteButton: Locator + readonly confirmDeleteButton: Locator - private readonly confirmDeleteAllDbButton: Locator + readonly confirmDeleteAllDbButton: Locator // TABLE / LIST SELECTORS - private readonly instanceRow: Locator + readonly instanceRow: Locator - private readonly selectAllCheckbox: Locator + readonly selectAllCheckbox: Locator - private readonly deleteButtonInPopover: Locator + readonly deleteButtonInPopover: Locator - public dbNameList: Locator + dbNameList: Locator - private readonly tableRowContent: Locator + readonly tableRowContent: Locator - private readonly editDatabaseButton: Locator + readonly editDatabaseButton: Locator // NAVIGATION SELECTORS - private readonly databasePageLink: Locator + readonly databasePageLink: Locator - private readonly rdiPageLink: Locator + readonly rdiPageLink: Locator // Additional – used for deletion by name - private readonly deleteDatabaseButton: Locator + readonly deleteDatabaseButton: Locator // MODULE - private readonly moduleTooltip: Locator - - // API instance if needed in child pages (unused in these methods but may be used externally) - protected readonly databaseAPIRequests: DatabaseAPIRequests + readonly moduleTooltip: Locator constructor(page: Page) { super(page) - this.databaseAPIRequests = new DatabaseAPIRequests() this.toast = new Toast(page) // BUTTONS & ACTION SELECTORS this.deleteRowButton = page.locator('[data-testid^="delete-instance-"]') - this.confirmDeleteButton = page.locator('[data-testid^="delete-instance-"]', { hasText: 'Remove' }) + this.confirmDeleteButton = page.locator( + '[data-testid^="delete-instance-"]', + { hasText: 'Remove' }, + ) this.confirmDeleteAllDbButton = page.getByTestId('delete-selected-dbs') // TABLE / LIST SELECTORS this.instanceRow = page.locator('[class*=euiTableRow-isSelectable]') - this.selectAllCheckbox = page.locator('[data-test-subj="checkboxSelectAll"]') + this.selectAllCheckbox = page.locator( + '[data-test-subj="checkboxSelectAll"]', + ) this.deleteButtonInPopover = page.locator('#deletePopover button') this.dbNameList = page.locator('[data-testid^="instance-name"]') - this.tableRowContent = page.locator('[data-test-subj="database-alias-column"]') + this.tableRowContent = page.locator( + '[data-test-subj="database-alias-column"]', + ) this.editDatabaseButton = page.locator('[data-testid^="edit-instance"]') // NAVIGATION SELECTORS @@ -67,7 +70,9 @@ export class BaseOverviewPage extends BasePage { this.rdiPageLink = page.getByTestId('home-tab-rdi-instances') // Additional – we alias deleteDatabaseButton to the same as deleteRowButton - this.deleteDatabaseButton = page.locator('[data-testid^="delete-instance-"]') + this.deleteDatabaseButton = page.locator( + '[data-testid^="delete-instance-"]', + ) // MODULE this.moduleTooltip = page.locator('.euiToolTipPopover') @@ -102,7 +107,7 @@ export class BaseOverviewPage extends BasePage { async deleteDatabaseByName(dbName: string): Promise { const count = await this.tableRowContent.count() - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i += 1) { const text = (await this.tableRowContent.nth(i).textContent()) || '' if (text.includes(dbName)) { // Assumes that the delete button for the row is located at index i-1. @@ -121,7 +126,7 @@ export class BaseOverviewPage extends BasePage { async clickOnEditDBByName(databaseName: string): Promise { const count = await this.dbNameList.count() - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i += 1) { const text = (await this.dbNameList.nth(i).textContent()) || '' if (text.includes(databaseName)) { await this.editDatabaseButton.nth(i).click() @@ -132,7 +137,9 @@ export class BaseOverviewPage extends BasePage { async checkModulesInTooltip(moduleNameList: string[]): Promise { for (const item of moduleNameList) { - await expect(this.moduleTooltip.locator('span', { hasText: `${item} v.` })).toBeVisible() + await expect( + this.moduleTooltip.locator('span', { hasText: `${item} v.` }), + ).toBeVisible() } } @@ -146,20 +153,28 @@ export class BaseOverviewPage extends BasePage { const databases: string[] = [] await expect(this.dbNameList).toBeVisible() const n = await this.dbNameList.count() - for (let k = 0; k < n; k++) { + for (let k = 0; k < n; k += 1) { const name = await this.dbNameList.nth(k).textContent() databases.push(name || '') } return databases } - async compareInstances(actualList: string[], sortedList: string[]): Promise { - for (let k = 0; k < actualList.length; k++) { + async compareInstances( + actualList: string[], + sortedList: string[], + ): Promise { + for (let k = 0; k < actualList.length; k += 1) { await expect(actualList[k].trim()).toEqual(sortedList[k].trim()) } } - getDatabaseNamesFromListByResult(listOfDb: DatabasesForImport, result: string): string[] { - return listOfDb.filter(element => element.result === result).map(item => item.name!) + getDatabaseNamesFromListByResult( + listOfDb: DatabasesForImport, + result: string, + ): string[] { + return listOfDb + .filter((element) => element.result === result) + .map((item) => item.name!) } } diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts index ef72c1e865..9eba42f98a 100644 --- a/tests/playwright/pageObjects/base-page.ts +++ b/tests/playwright/pageObjects/base-page.ts @@ -1,12 +1,6 @@ -import {Locator, Page, expect} from '@playwright/test' +import { Locator, Page, expect } from '@playwright/test' -declare global { - interface Window { - windowId?: string - } -} - -export class BasePage { +export class BasePage { page: Page constructor(page: Page) { @@ -33,8 +27,8 @@ export class BasePage { await this.page.fill(selector, value) } - async getText(locator: Locator): Promise { - return locator.textContent() + async getText(locator: Locator): Promise { + return locator.textContent() } async isVisible(selctor: string): Promise { @@ -44,34 +38,26 @@ export class BasePage { async getByTestId(testId: string): Promise { return this.page.getByTestId(testId) } + async waitForLocatorVisible(locator: Locator, timeout = 6000) { await expect(locator).toBeVisible({ timeout }) } + async waitForLocatorNotVisible(locator: Locator, timeout = 6000) { await expect(locator).not.toBeVisible({ timeout }) } - async goBackHistor(): Promise{ + + async goBackHistor(): Promise { await this.page.goBack() - }; - async elementExistsSelector( selector: string): Promise { + } + + async elementExistsSelector(selector: string): Promise { const count = await this.page.locator(selector).count() return count > 0 } - async elementExistsLocator( locator: Locator): Promise { + async elementExistsLocator(locator: Locator): Promise { const count = await locator.count() return count > 0 } - - async waitForLocatorVisible(locator: Locator, timeout = 6000) { - await expect(locator).toBeVisible({ timeout }) - } - async waitForLocatorNotVisible(locator: Locator, timeout = 6000) { - await expect(locator).not.toBeVisible({ timeout }) - } - - async getWindowId():Promise { - return this.page.evaluate(() => window.windowId) - - } } diff --git a/tests/playwright/pageObjects/browser-page.ts b/tests/playwright/pageObjects/browser-page.ts index fd63157510..ef6b4f3703 100755 --- a/tests/playwright/pageObjects/browser-page.ts +++ b/tests/playwright/pageObjects/browser-page.ts @@ -1,260 +1,277 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable @typescript-eslint/lines-between-class-members */ import { expect, Locator, Page } from '@playwright/test' -// import { Common } from '../helpers/common' -import { AddElementInList } from '../helpers/constants' -import {Toast } from './components/common/toast' +import { Toast } from './components/common/toast' -// import { BulkActions, TreeView } from './components/browser' -// import { AddNewKeyParameters , HashKeyParameters ,StreamKeyParameters, -// SetKeyParameters , SortedSetKeyParameters ,ListKeyParameters, StringKeyParameters, AddKeyArguments, KeyData} from '../types' import { BasePage } from './base-page' +import { AddElementInList } from '../helpers/constants' export class BrowserPage extends BasePage { - // private readonly bulkActions: BulkActions - // private readonly treeView: TreeView - page: Page private toast: Toast // CSS Selectors - private readonly cssSelectorGrid: Locator - private readonly cssSelectorRows: Locator - private readonly cssSelectorKey: Locator - private readonly cssFilteringLabel: Locator - private readonly cssJsonValue: Locator - private readonly cssRowInVirtualizedTable: Locator - private readonly cssVirtualTableRow: Locator - private readonly cssKeyBadge: Locator - private readonly cssKeyTtl: Locator - private readonly cssKeySize: Locator - private readonly cssRemoveSuggestionItem: Locator + public readonly cssSelectorGrid: Locator + public readonly cssSelectorRows: Locator + public readonly cssSelectorKey: Locator + public readonly cssFilteringLabel: Locator + public readonly cssJsonValue: Locator + public readonly cssRowInVirtualizedTable: Locator + public readonly cssVirtualTableRow: Locator + public readonly cssKeyBadge: Locator + public readonly cssKeyTtl: Locator + public readonly cssKeySize: Locator + public readonly cssRemoveSuggestionItem: Locator // BUTTONS - private readonly applyButton: Locator - private readonly deleteKeyButton: Locator - private readonly submitDeleteKeyButton: Locator - private readonly confirmDeleteKeyButton: Locator - private readonly editKeyTTLButton: Locator - private readonly refreshKeysButton: Locator - private readonly refreshKeyButton: Locator - private readonly editKeyNameButton: Locator - private readonly editKeyValueButton: Locator - private readonly closeKeyButton: Locator - private readonly plusAddKeyButton: Locator - private readonly addKeyValueItemsButton: Locator - private readonly saveHashFieldButton: Locator - private readonly saveMemberButton: Locator - private readonly searchButtonInKeyDetails: Locator - private readonly addKeyButton: Locator - private readonly keyTypeDropDown: Locator - private readonly confirmRemoveHashFieldButton: Locator - private readonly removeSetMemberButton: Locator - private readonly removeHashFieldButton: Locator - private readonly removeZsetMemberButton: Locator - private readonly confirmRemoveSetMemberButton: Locator - private readonly confirmRemoveZSetMemberButton: Locator - private readonly saveElementButton: Locator - private readonly removeElementFromListIconButton: Locator - private readonly removeElementFromListButton: Locator - private readonly confirmRemoveListElementButton: Locator - private readonly removeElementFromListSelect: Locator - private readonly addJsonObjectButton: Locator - private readonly addJsonFieldButton: Locator - private readonly expandJsonObject: Locator - private readonly scoreButton: Locator - private readonly sortingButton: Locator - private readonly editJsonObjectButton: Locator - private readonly applyEditButton: Locator - private readonly cancelEditButton: Locator - private readonly scanMoreButton: Locator - private readonly resizeBtnKeyList: Locator - private readonly treeViewButton: Locator - private readonly browserViewButton: Locator - private readonly searchButton: Locator - private readonly clearFilterButton: Locator - private readonly fullScreenModeButton: Locator - private readonly closeRightPanel: Locator - private readonly addNewStreamEntry: Locator - private readonly removeEntryButton: Locator - private readonly confirmRemoveEntryButton: Locator - private readonly clearStreamEntryInputs: Locator - private readonly saveGroupsButton: Locator - private readonly acknowledgeButton: Locator - private readonly confirmAcknowledgeButton: Locator - private readonly claimPendingMessageButton: Locator - private readonly submitButton: Locator - private readonly consumerDestinationSelect: Locator - private readonly removeConsumerButton: Locator - private readonly removeConsumerGroupButton: Locator - private readonly optionalParametersSwitcher: Locator - private readonly forceClaimCheckbox: Locator - private readonly editStreamLastIdButton: Locator - private readonly saveButton: Locator - private readonly bulkActionsButton: Locator - private readonly editHashButton: Locator - private readonly editHashFieldTtlButton: Locator - private readonly editZsetButton: Locator - private readonly editListButton: Locator - private readonly cancelStreamGroupBtn: Locator - private readonly patternModeBtn: Locator - private readonly redisearchModeBtn: Locator - private readonly showFilterHistoryBtn: Locator - private readonly clearFilterHistoryBtn: Locator - private readonly loadSampleDataBtn: Locator - private readonly executeBulkKeyLoadBtn: Locator - private readonly backToBrowserBtn: Locator - private readonly loadAllBtn: Locator - private readonly downloadAllValueBtn: Locator - private readonly openTutorialsBtn: Locator - private readonly keyItem: Locator - private readonly columnsBtn: Locator + public readonly applyButton: Locator + public readonly cancelButton: Locator + public readonly deleteKeyButton: Locator + public readonly submitDeleteKeyButton: Locator + public readonly confirmDeleteKeyButton: Locator + public readonly editKeyTTLButton: Locator + public readonly refreshKeysButton: Locator + public readonly refreshKeyButton: Locator + public readonly editKeyNameButton: Locator + public readonly editKeyValueButton: Locator + public readonly closeKeyButton: Locator + public readonly plusAddKeyButton: Locator + public readonly addKeyValueItemsButton: Locator + public readonly saveHashFieldButton: Locator + public readonly saveMemberButton: Locator + public readonly searchButtonInKeyDetails: Locator + public readonly addKeyButton: Locator + public readonly keyTypeDropDown: Locator + public readonly confirmRemoveHashFieldButton: Locator + public readonly removeSetMemberButton: Locator + public readonly removeHashFieldButton: Locator + public readonly removeZsetMemberButton: Locator + public readonly confirmRemoveSetMemberButton: Locator + public readonly confirmRemoveZSetMemberButton: Locator + public readonly saveElementButton: Locator + public readonly removeElementFromListIconButton: Locator + public readonly removeElementFromListButton: Locator + public readonly confirmRemoveListElementButton: Locator + public readonly removeElementFromListSelect: Locator + public readonly addJsonObjectButton: Locator + public readonly addJsonFieldButton: Locator + public readonly expandJsonObject: Locator + public readonly scoreButton: Locator + public readonly sortingButton: Locator + public readonly editJsonObjectButton: Locator + public readonly applyEditButton: Locator + public readonly cancelEditButton: Locator + public readonly scanMoreButton: Locator + public readonly resizeBtnKeyList: Locator + public readonly treeViewButton: Locator + public readonly browserViewButton: Locator + public readonly searchButton: Locator + public readonly clearFilterButton: Locator + public readonly fullScreenModeButton: Locator + public readonly closeRightPanel: Locator + public readonly addNewStreamEntry: Locator + public readonly removeEntryButton: Locator + public readonly confirmRemoveEntryButton: Locator + public readonly clearStreamEntryInputs: Locator + public readonly saveGroupsButton: Locator + public readonly acknowledgeButton: Locator + public readonly confirmAcknowledgeButton: Locator + public readonly claimPendingMessageButton: Locator + public readonly submitButton: Locator + public readonly consumerDestinationSelect: Locator + public readonly removeConsumerButton: Locator + public readonly removeConsumerGroupButton: Locator + public readonly optionalParametersSwitcher: Locator + public readonly forceClaimCheckbox: Locator + public readonly editStreamLastIdButton: Locator + public readonly saveButton: Locator + public readonly bulkActionsButton: Locator + public readonly editHashButton: Locator + public readonly editHashFieldTtlButton: Locator + public readonly editZsetButton: Locator + public readonly editListButton: Locator + public readonly cancelStreamGroupBtn: Locator + public readonly patternModeBtn: Locator + public readonly redisearchModeBtn: Locator + public readonly showFilterHistoryBtn: Locator + public readonly clearFilterHistoryBtn: Locator + public readonly loadSampleDataBtn: Locator + public readonly executeBulkKeyLoadBtn: Locator + public readonly backToBrowserBtn: Locator + public readonly loadAllBtn: Locator + public readonly downloadAllValueBtn: Locator + public readonly openTutorialsBtn: Locator + public readonly keyItem: Locator + public readonly columnsBtn: Locator // CONTAINERS - private readonly streamGroupsContainer: Locator - private readonly streamConsumersContainer: Locator - private readonly breadcrumbsContainer: Locator - private readonly virtualTableContainer: Locator - private readonly streamEntriesContainer: Locator - private readonly streamMessagesContainer: Locator - private readonly loader: Locator - private readonly newIndexPanel: Locator + public readonly streamGroupsContainer: Locator + public readonly streamConsumersContainer: Locator + public readonly breadcrumbsContainer: Locator + public readonly virtualTableContainer: Locator + public readonly streamEntriesContainer: Locator + public readonly streamMessagesContainer: Locator + public readonly loader: Locator + public readonly newIndexPanel: Locator // LINKS - private readonly internalLinkToWorkbench: Locator - private readonly userSurveyLink: Locator - private readonly redisearchFreeLink: Locator - private readonly guideLinksBtn: Locator + public readonly internalLinkToWorkbench: Locator + public readonly userSurveyLink: Locator + public readonly redisearchFreeLink: Locator + public readonly guideLinksBtn: Locator // OPTION ELEMENTS - private readonly stringOption: Locator - private readonly jsonOption: Locator - private readonly setOption: Locator - private readonly zsetOption: Locator - private readonly listOption: Locator - private readonly hashOption: Locator - private readonly streamOption: Locator - private readonly removeFromHeadSelection: Locator - private readonly filterOptionType: Locator - private readonly filterByKeyTypeDropDown: Locator - private readonly filterAllKeyType: Locator - private readonly consumerOption: Locator - private readonly claimTimeOptionSelect: Locator - private readonly relativeTimeOption: Locator - private readonly timestampOption: Locator - private readonly formatSwitcher: Locator - private readonly formatSwitcherIcon: Locator - private readonly refreshIndexButton: Locator - private readonly selectIndexDdn: Locator - private readonly createIndexBtn: Locator - private readonly cancelIndexCreationBtn: Locator - private readonly confirmIndexCreationBtn: Locator - private readonly resizeTrigger: Locator - private readonly filterHistoryOption: Locator - private readonly filterHistoryItemText: Locator + public readonly stringOption: Locator + public readonly jsonOption: Locator + public readonly setOption: Locator + public readonly zsetOption: Locator + public readonly listOption: Locator + public readonly hashOption: Locator + public readonly streamOption: Locator + public readonly removeFromHeadSelection: Locator + public readonly filterOptionType: Locator + public readonly filterByKeyTypeDropDown: Locator + public readonly filterAllKeyType: Locator + public readonly consumerOption: Locator + public readonly claimTimeOptionSelect: Locator + public readonly relativeTimeOption: Locator + public readonly timestampOption: Locator + public readonly formatSwitcher: Locator + public readonly formatSwitcherIcon: Locator + public readonly refreshIndexButton: Locator + public readonly selectIndexDdn: Locator + public readonly createIndexBtn: Locator + public readonly cancelIndexCreationBtn: Locator + public readonly confirmIndexCreationBtn: Locator + public readonly resizeTrigger: Locator + public readonly filterHistoryOption: Locator + public readonly filterHistoryItemText: Locator // TABS - private readonly streamTabGroups: Locator - private readonly streamTabConsumers: Locator - private readonly streamTabs: Locator + public readonly streamTabGroups: Locator + public readonly streamTabConsumers: Locator + public readonly streamTabs: Locator // TEXT INPUTS - private readonly addKeyNameInput: Locator - private readonly keyNameInput: Locator - private readonly keyTTLInput: Locator - private readonly editKeyTTLInput: Locator - private readonly ttlText: Locator - private readonly hashFieldValueInput: Locator - private readonly hashFieldNameInput: Locator - private readonly hashFieldValueEditor: Locator - private readonly hashTtlFieldInput: Locator - private readonly listKeyElementEditorInput: Locator - private readonly stringKeyValueInput: Locator - private readonly jsonKeyValueInput: Locator - private readonly jsonUploadInput: Locator - private readonly setMemberInput: Locator - private readonly zsetMemberScoreInput: Locator - private readonly filterByPatterSearchInput: Locator + public readonly addKeyNameInput: Locator + public readonly keyNameInput: Locator + public readonly keyTTLInput: Locator + public readonly editKeyTTLInput: Locator + public readonly ttlText: Locator + public readonly hashFieldValueInput: Locator + public readonly hashFieldNameInput: Locator + public readonly hashFieldValueEditor: Locator + public readonly hashTtlFieldInput: Locator + public readonly listKeyElementEditorInput: Locator + public readonly stringKeyValueInput: Locator + public readonly jsonKeyValueInput: Locator + public readonly jsonUploadInput: Locator + public readonly setMemberInput: Locator + public readonly zsetMemberScoreInput: Locator + public readonly filterByPatterSearchInput: Locator + public readonly hashFieldInput: Locator + public readonly hashValueInput: Locator + public readonly searchInput: Locator + public readonly jsonKeyInput: Locator + public readonly jsonValueInput: Locator + public readonly countInput: Locator + public readonly streamEntryId: Locator + public readonly streamField: Locator + public readonly streamValue: Locator + public readonly addAdditionalElement: Locator + public readonly streamFieldsValues: Locator + public readonly streamEntryIDDateValue: Locator + public readonly groupNameInput: Locator + public readonly consumerIdInput: Locator + public readonly streamMinIdleTimeInput: Locator + public readonly claimIdleTimeInput: Locator + public readonly claimRetryCountInput: Locator + public readonly lastIdInput: Locator + public readonly inlineItemEditor: Locator + public readonly indexNameInput: Locator + public readonly prefixFieldInput: Locator + public readonly indexIdentifierInput: Locator // TEXT ELEMENTS - private readonly keySizeDetails: Locator - private readonly keyLengthDetails: Locator - private readonly keyNameInTheList: Locator - private readonly hashFieldsList: Locator - private readonly hashValuesList: Locator - private readonly hashField: Locator - private readonly hashFieldValue: Locator - private readonly setMembersList: Locator - private readonly zsetMembersList: Locator - private readonly zsetScoresList: Locator - private readonly listElementsList: Locator - private readonly jsonKeyValue: Locator - private readonly jsonError: Locator - private readonly tooltip: Locator - private readonly dialog: Locator - private readonly noResultsFound: Locator - private readonly noResultsFoundOnly: Locator - private readonly searchAdvices: Locator - private readonly keysNumberOfResults: Locator - private readonly scannedValue: Locator - private readonly totalKeysNumber: Locator - private readonly keyDetailsBadge: Locator - private readonly modulesTypeDetails: Locator - private readonly filteringLabel: Locator - private readonly keysSummary: Locator - private readonly multiSearchArea: Locator - private readonly keyDetailsHeader: Locator - private readonly keyListTable: Locator - private readonly keyListMessage: Locator - private readonly keyDetailsTable: Locator - private readonly keyNameFormDetails: Locator - private readonly keyDetailsTTL: Locator - private readonly progressLine: Locator - private readonly progressKeyList: Locator - private readonly jsonScalarValue: Locator - private readonly noKeysToDisplayText: Locator - private readonly streamEntryDate: Locator - private readonly streamEntryIdValue: Locator - private readonly streamFields: Locator - private readonly streamVirtualContainer: Locator - private readonly streamEntryFields: Locator - private readonly confirmationMessagePopover: Locator - private readonly streamGroupId: Locator - private readonly streamGroupName: Locator - private readonly streamMessage: Locator - private readonly streamConsumerName: Locator - private readonly consumerGroup: Locator - private readonly entryIdInfoIcon: Locator - private readonly entryIdError: Locator - private readonly pendingCount: Locator - private readonly streamRangeBar: Locator - private readonly rangeLeftTimestamp: Locator - private readonly rangeRightTimestamp: Locator - private readonly jsonValue: Locator - private readonly stringValueAsJson: Locator + public readonly keySizeDetails: Locator + public readonly keyLengthDetails: Locator + public readonly keyNameInTheList: Locator + public readonly hashFieldsList: Locator + public readonly hashValuesList: Locator + public readonly hashField: Locator + public readonly hashFieldValue: Locator + public readonly setMembersList: Locator + public readonly zsetMembersList: Locator + public readonly zsetScoresList: Locator + public readonly listElementsList: Locator + public readonly jsonKeyValue: Locator + public readonly jsonError: Locator + public readonly tooltip: Locator + public readonly dialog: Locator + public readonly noResultsFound: Locator + public readonly noResultsFoundOnly: Locator + public readonly searchAdvices: Locator + public readonly keysNumberOfResults: Locator + public readonly scannedValue: Locator + public readonly totalKeysNumber: Locator + public readonly keyDetailsBadge: Locator + public readonly modulesTypeDetails: Locator + public readonly filteringLabel: Locator + public readonly keysSummary: Locator + public readonly multiSearchArea: Locator + public readonly keyDetailsHeader: Locator + public readonly keyListTable: Locator + public readonly keyListMessage: Locator + public readonly keyDetailsTable: Locator + public readonly keyNameFormDetails: Locator + public readonly keyDetailsTTL: Locator + public readonly progressLine: Locator + public readonly progressKeyList: Locator + public readonly jsonScalarValue: Locator + public readonly noKeysToDisplayText: Locator + public readonly streamEntryDate: Locator + public readonly streamEntryIdValue: Locator + public readonly streamFields: Locator + public readonly streamVirtualContainer: Locator + public readonly streamEntryFields: Locator + public readonly confirmationMessagePopover: Locator + public readonly streamGroupId: Locator + public readonly streamGroupName: Locator + public readonly streamMessage: Locator + public readonly streamConsumerName: Locator + public readonly consumerGroup: Locator + public readonly entryIdInfoIcon: Locator + public readonly entryIdError: Locator + public readonly pendingCount: Locator + public readonly streamRangeBar: Locator + public readonly rangeLeftTimestamp: Locator + public readonly rangeRightTimestamp: Locator + public readonly jsonValue: Locator + public readonly stringValueAsJson: Locator // POPUPS - private readonly changeValueWarning: Locator + public readonly changeValueWarning: Locator // TABLE - private readonly keyListItem: Locator + public readonly keyListItem: Locator // DIALOG - private readonly noReadySearchDialogTitle: Locator + public readonly noReadySearchDialogTitle: Locator // CHECKBOXES - private readonly showTtlCheckbox: Locator - private readonly showTtlColumnCheckbox: Locator - private readonly showSizeColumnCheckbox: Locator + public readonly showTtlCheckbox: Locator + public readonly showTtlColumnCheckbox: Locator + public readonly showSizeColumnCheckbox: Locator // UTILITY FUNCTIONS - private readonly getHashTtlFieldInput: (fieldName: string) => Locator - private readonly getListElementInput: (count: number) => Locator - private readonly getKeySize: (keyName: string) => Locator - private readonly getKeyTTl: (keyName: string) => Locator + public readonly getHashTtlFieldInput: (fieldName: string) => Locator + public readonly getListElementInput: (count: number) => Locator + public readonly getKeySize: (keyName: string) => Locator + public readonly getKeyTTl: (keyName: string) => Locator constructor(page: Page) { super(page) this.page = page this.toast = new Toast(page) - // this.bulkActions = new BulkActions(page) - // this.treeView = new TreeView(page) // CSS Selectors this.cssSelectorGrid = page.locator('[aria-label="grid"]') @@ -267,10 +284,13 @@ export class BrowserPage extends BasePage { this.cssKeyBadge = page.locator('[data-testid^="badge-"]') this.cssKeyTtl = page.locator('[data-testid^="ttl-"]') this.cssKeySize = page.locator('[data-testid^="size-"]') - this.cssRemoveSuggestionItem = page.locator('[data-testid^="remove-suggestion-item-"]') + this.cssRemoveSuggestionItem = page.locator( + '[data-testid^="remove-suggestion-item-"]', + ) // BUTTONS this.applyButton = page.getByTestId('apply-btn') + this.cancelButton = page.getByTestId('cancel-btn') this.deleteKeyButton = page.getByTestId('delete-key-btn') this.submitDeleteKeyButton = page.getByTestId('submit-delete-key') this.confirmDeleteKeyButton = page.getByTestId('delete-key-confirm-btn') @@ -281,23 +301,40 @@ export class BrowserPage extends BasePage { this.editKeyValueButton = page.getByTestId('edit-key-value-btn') this.closeKeyButton = page.getByTestId('close-key-btn') this.plusAddKeyButton = page.getByTestId('btn-add-key') - this.addKeyValueItemsButton = page.getByTestId('add-key-value-items-btn') + this.addKeyValueItemsButton = page.getByTestId( + 'add-key-value-items-btn', + ) this.saveHashFieldButton = page.getByTestId('save-fields-btn') this.saveMemberButton = page.getByTestId('save-members-btn') this.searchButtonInKeyDetails = page.getByTestId('search-button') - this.addKeyButton = page.getByTestId('add-key-hash-btn') - this.keyTypeDropDown = page.locator('fieldset button.euiSuperSelectControl') - this.confirmRemoveHashFieldButton = page.locator('[data-testid^="remove-hash-button-"] span') + this.addKeyButton = page.locator('button', { + hasText: 'Add Key', + }) + this.keyTypeDropDown = page.locator( + 'fieldset button.euiSuperSelectControl', + ) + this.confirmRemoveHashFieldButton = page.locator( + '[data-testid^="remove-hash-button-"] span', + ) this.removeSetMemberButton = page.getByTestId('set-remove-btn') this.removeHashFieldButton = page.getByTestId('remove-hash-button') this.removeZsetMemberButton = page.getByTestId('zset-remove-button') - this.confirmRemoveSetMemberButton = page.locator('[data-testid^="set-remove-btn-"] span') - this.confirmRemoveZSetMemberButton = page.locator('[data-testid^="zset-remove-button-"] span') + this.confirmRemoveSetMemberButton = page.locator( + '[data-testid^="set-remove-btn-"] span', + ) + this.confirmRemoveZSetMemberButton = page.locator( + '[data-testid^="zset-remove-button-"] span', + ) this.saveElementButton = page.getByTestId('save-elements-btn') - this.removeElementFromListIconButton = page.getByTestId('remove-key-value-items-btn') - this.removeElementFromListButton = page.getByTestId('remove-elements-btn') + this.removeElementFromListIconButton = page.getByTestId( + 'remove-key-value-items-btn', + ) + this.removeElementFromListButton = page.getByTestId( + 'remove-elements-btn', + ) this.confirmRemoveListElementButton = page.getByTestId('remove-submit') - this.removeElementFromListSelect = page.getByTestId('destination-select') + this.removeElementFromListSelect = + page.getByTestId('destination-select') this.addJsonObjectButton = page.getByTestId('add-object-btn') this.addJsonFieldButton = page.getByTestId('add-field-btn') this.expandJsonObject = page.getByTestId('expand-object') @@ -307,7 +344,9 @@ export class BrowserPage extends BasePage { this.applyEditButton = page.getByTestId('apply-edit-btn') this.cancelEditButton = page.getByTestId('cancel-edit-btn') this.scanMoreButton = page.getByTestId('scan-more') - this.resizeBtnKeyList = page.locator('[data-test-subj="resize-btn-keyList-keyDetails"]') + this.resizeBtnKeyList = page.locator( + '[data-test-subj="resize-btn-keyList-keyDetails"]', + ) this.treeViewButton = page.getByTestId('view-type-list-btn') this.browserViewButton = page.getByTestId('view-type-browser-btn') this.searchButton = page.getByTestId('search-btn') @@ -315,24 +354,40 @@ export class BrowserPage extends BasePage { this.fullScreenModeButton = page.getByTestId('toggle-full-screen') this.closeRightPanel = page.getByTestId('close-right-panel-btn') this.addNewStreamEntry = page.getByTestId('add-key-value-items-btn') - this.removeEntryButton = page.locator('[data-testid^="remove-entry-button-"]') - this.confirmRemoveEntryButton = page.locator('[data-testid^="remove-entry-button-"]').filter({ hasText: 'Remove' }) + this.removeEntryButton = page.locator( + '[data-testid^="remove-entry-button-"]', + ) + this.confirmRemoveEntryButton = page + .locator('[data-testid^="remove-entry-button-"]') + .filter({ hasText: 'Remove' }) this.clearStreamEntryInputs = page.getByTestId('remove-item') this.saveGroupsButton = page.getByTestId('save-groups-btn') this.acknowledgeButton = page.getByTestId('acknowledge-btn') this.confirmAcknowledgeButton = page.getByTestId('acknowledge-submit') - this.claimPendingMessageButton = page.getByTestId('claim-pending-message') + this.claimPendingMessageButton = page.getByTestId( + 'claim-pending-message', + ) this.submitButton = page.getByTestId('btn-submit') this.consumerDestinationSelect = page.getByTestId('destination-select') - this.removeConsumerButton = page.locator('[data-testid^="remove-consumer-button"]') - this.removeConsumerGroupButton = page.locator('[data-testid^="remove-groups-button"]') - this.optionalParametersSwitcher = page.getByTestId('optional-parameters-switcher') - this.forceClaimCheckbox = page.getByTestId('force-claim-checkbox').locator('..') + this.removeConsumerButton = page.locator( + '[data-testid^="remove-consumer-button"]', + ) + this.removeConsumerGroupButton = page.locator( + '[data-testid^="remove-groups-button"]', + ) + this.optionalParametersSwitcher = page.getByTestId( + 'optional-parameters-switcher', + ) + this.forceClaimCheckbox = page + .getByTestId('force-claim-checkbox') + .locator('..') this.editStreamLastIdButton = page.getByTestId('stream-group_edit-btn') this.saveButton = page.getByTestId('save-btn') this.bulkActionsButton = page.getByTestId('btn-bulk-actions') this.editHashButton = page.locator('[data-testid^="hash_edit-btn-"]') - this.editHashFieldTtlButton = page.locator('[data-testid^="hash-ttl_edit-btn-"]') + this.editHashFieldTtlButton = page.locator( + '[data-testid^="hash-ttl_edit-btn-"]', + ) this.editZsetButton = page.locator('[data-testid^="zset_edit-btn-"]') this.editListButton = page.locator('[data-testid^="list_edit-btn-"]') this.cancelStreamGroupBtn = page.getByTestId('cancel-stream-groups-btn') @@ -341,26 +396,38 @@ export class BrowserPage extends BasePage { this.showFilterHistoryBtn = page.getByTestId('show-suggestions-btn') this.clearFilterHistoryBtn = page.getByTestId('clear-history-btn') this.loadSampleDataBtn = page.getByTestId('load-sample-data-btn') - this.executeBulkKeyLoadBtn = page.getByTestId('load-sample-data-btn-confirm') + this.executeBulkKeyLoadBtn = page.getByTestId( + 'load-sample-data-btn-confirm', + ) this.backToBrowserBtn = page.getByTestId('back-right-panel-btn') this.loadAllBtn = page.getByTestId('load-all-value-btn') this.downloadAllValueBtn = page.getByTestId('download-all-value-btn') this.openTutorialsBtn = page.getByTestId('explore-msg-btn') - this.keyItem = page.locator('[data-testid*="node-item"][data-testid*="keys:"]') + this.keyItem = page.locator( + '[data-testid*="node-item"][data-testid*="keys:"]', + ) this.columnsBtn = page.getByTestId('btn-columns-actions') // CONTAINERS this.streamGroupsContainer = page.getByTestId('stream-groups-container') - this.streamConsumersContainer = page.getByTestId('stream-consumers-container') + this.streamConsumersContainer = page.getByTestId( + 'stream-consumers-container', + ) this.breadcrumbsContainer = page.getByTestId('breadcrumbs-container') this.virtualTableContainer = page.getByTestId('virtual-table-container') - this.streamEntriesContainer = page.getByTestId('stream-entries-container') - this.streamMessagesContainer = page.getByTestId('stream-messages-container') + this.streamEntriesContainer = page.getByTestId( + 'stream-entries-container', + ) + this.streamMessagesContainer = page.getByTestId( + 'stream-messages-container', + ) this.loader = page.getByTestId('type-loading') this.newIndexPanel = page.getByTestId('create-index-panel') // LINKS - this.internalLinkToWorkbench = page.getByTestId('internal-workbench-link') + this.internalLinkToWorkbench = page.getByTestId( + 'internal-workbench-link', + ) this.userSurveyLink = page.getByTestId('user-survey-link') this.redisearchFreeLink = page.getByTestId('get-started-link') this.guideLinksBtn = page.locator('[data-testid^="guide-button-"]') @@ -374,19 +441,29 @@ export class BrowserPage extends BasePage { this.hashOption = page.locator('#hash') this.streamOption = page.locator('#stream') this.removeFromHeadSelection = page.locator('#HEAD') - this.filterOptionType = page.locator('[data-test-subj^="filter-option-type-"]') - this.filterByKeyTypeDropDown = page.getByTestId('select-filter-key-type') + this.filterOptionType = page.locator( + '[data-test-subj^="filter-option-type-"]', + ) + this.filterByKeyTypeDropDown = page.getByTestId( + 'select-filter-key-type', + ) this.filterAllKeyType = page.locator('#all') this.consumerOption = page.getByTestId('consumer-option') this.claimTimeOptionSelect = page.getByTestId('time-option-select') this.relativeTimeOption = page.locator('#idle') this.timestampOption = page.locator('#time') this.formatSwitcher = page.getByTestId('select-format-key-value') - this.formatSwitcherIcon = page.locator('[data-testid^="key-value-formatter-option-selected"]') + this.formatSwitcherIcon = page.locator( + '[data-testid^="key-value-formatter-option-selected"]', + ) this.refreshIndexButton = page.getByTestId('refresh-indexes-btn') - this.selectIndexDdn = page.locator('[data-testid="select-index-placeholder"],[data-testid="select-search-mode"]') + this.selectIndexDdn = page.locator( + '[data-testid="select-index-placeholder"],[data-testid="select-search-mode"]', + ) this.createIndexBtn = page.getByTestId('create-index-btn') - this.cancelIndexCreationBtn = page.getByTestId('create-index-cancel-btn') + this.cancelIndexCreationBtn = page.getByTestId( + 'create-index-cancel-btn', + ) this.confirmIndexCreationBtn = page.getByTestId('create-index-btn') this.resizeTrigger = page.locator('[data-testid^="resize-trigger-"]') this.filterHistoryOption = page.getByTestId('suggestion-item-') @@ -409,40 +486,46 @@ export class BrowserPage extends BasePage { this.hashTtlFieldInput = page.getByTestId('hash-ttl') this.listKeyElementEditorInput = page.getByTestId('list_value-editor-') this.stringKeyValueInput = page.getByTestId('string-value') - this.jsonKeyValueInput = page.locator('[data-mode-id="json"]') + this.jsonKeyValueInput = page.locator( + 'div[data-mode-id=json] textarea', + ) this.jsonUploadInput = page.getByTestId('upload-input-file') this.setMemberInput = page.getByTestId('member-name') this.zsetMemberScoreInput = page.getByTestId('member-score') this.filterByPatterSearchInput = page.getByTestId('search-key') - // this.hashFieldInput = page.getByTestId('hash-field') - // this.hashValueInput = page.getByTestId('hash-value') - // this.searchInput = page.getByTestId('search') - // this.jsonKeyInput = page.getByTestId('json-key') - // this.jsonValueInput = page.getByTestId('json-value') - // this.countInput = page.getByTestId('count-input') - // this.streamEntryId = page.getByTestId('entryId') - // this.streamField = page.getByTestId('field-name') - // this.streamValue = page.getByTestId('field-value') - // this.addAdditionalElement = page.getByTestId('add-item') - // this.streamFieldsValues = page.getByTestId('stream-entry-field-') - // this.streamEntryIDDateValue = page.locator('[data-testid^="stream-entry-"][data-testid$="date"]') - // this.groupNameInput = page.getByTestId('group-name-field') - // this.consumerIdInput = page.getByTestId('id-field') - // this.streamMinIdleTimeInput = page.getByTestId('min-idle-time') - // this.claimIdleTimeInput = page.getByTestId('time-count') - // this.claimRetryCountInput = page.getByTestId('retry-count') - // this.lastIdInput = page.getByTestId('last-id-field') - // this.inlineItemEditor = page.getByTestId('inline-item-editor') - // this.indexNameInput = page.getByTestId('index-name') - // this.prefixFieldInput = page.locator('[data-test-subj="comboBoxInput"]') - // this.indexIdentifierInput = page.getByTestId('identifier-') + this.hashFieldInput = page.getByTestId('hash-field') + this.hashValueInput = page.getByTestId('hash-value') + this.searchInput = page.getByTestId('search') + this.jsonKeyInput = page.getByTestId('json-key') + this.jsonValueInput = page.getByTestId('json-value') + this.countInput = page.getByTestId('count-input') + this.streamEntryId = page.getByTestId('entryId') + this.streamField = page.getByTestId('field-name') + this.streamValue = page.getByTestId('field-value') + this.addAdditionalElement = page.getByTestId('add-item') + this.streamFieldsValues = page.getByTestId('stream-entry-field-') + this.streamEntryIDDateValue = page.locator( + '[data-testid^="stream-entry-"][data-testid$="date"]', + ) + this.groupNameInput = page.getByTestId('group-name-field') + this.consumerIdInput = page.getByTestId('id-field') + this.streamMinIdleTimeInput = page.getByTestId('min-idle-time') + this.claimIdleTimeInput = page.getByTestId('time-count') + this.claimRetryCountInput = page.getByTestId('retry-count') + this.lastIdInput = page.getByTestId('last-id-field') + this.inlineItemEditor = page.getByTestId('inline-item-editor') + this.indexNameInput = page.getByTestId('index-name') + this.prefixFieldInput = page.locator('[data-test-subj="comboBoxInput"]') + this.indexIdentifierInput = page.getByTestId('identifier-') // TEXT ELEMENTS this.keySizeDetails = page.getByTestId('key-size-text') this.keyLengthDetails = page.getByTestId('key-length-text') this.keyNameInTheList = this.cssSelectorKey this.hashFieldsList = page.getByTestId('hash-field-').locator('span') - this.hashValuesList = page.getByTestId('hash_content-value-').locator('span') + this.hashValuesList = page + .getByTestId('hash_content-value-') + .locator('span') this.hashField = page.getByTestId('hash-field-').first() this.hashFieldValue = page.getByTestId('hash_content-value-') this.setMembersList = page.getByTestId('set-member-value-') @@ -459,7 +542,9 @@ export class BrowserPage extends BasePage { this.keysNumberOfResults = page.getByTestId('keys-number-of-results') this.scannedValue = page.getByTestId('keys-number-of-scanned') this.totalKeysNumber = page.getByTestId('keys-total') - this.keyDetailsBadge = page.locator('.key-details-header .euiBadge__text') + this.keyDetailsBadge = page.locator( + '.key-details-header .euiBadge__text', + ) this.modulesTypeDetails = page.getByTestId('modules-type-details') this.filteringLabel = page.getByTestId('badge-') this.keysSummary = page.getByTestId('keys-summary') @@ -474,15 +559,29 @@ export class BrowserPage extends BasePage { this.progressKeyList = page.getByTestId('progress-key-list') this.jsonScalarValue = page.getByTestId('json-scalar-value') this.noKeysToDisplayText = page.getByTestId('no-result-found-msg') - this.streamEntryDate = page.locator('[data-testid*="-date"][data-testid*="stream-entry"]') - this.streamEntryIdValue = page.locator('.streamItemId[data-testid*="stream-entry"]') - this.streamFields = page.locator('[data-test-subj="stream-entries-container"] .truncateText') - this.streamVirtualContainer = page.locator('[data-testid="virtual-grid-container"] div div').first() + this.streamEntryDate = page.locator( + '[data-testid*="-date"][data-testid*="stream-entry"]', + ) + this.streamEntryIdValue = page.locator( + '.streamItemId[data-testid*="stream-entry"]', + ) + this.streamFields = page.locator( + '[data-test-subj="stream-entries-container"] .truncateText', + ) + this.streamVirtualContainer = page + .locator('[data-testid="virtual-grid-container"] div div') + .first() this.streamEntryFields = page.getByTestId('stream-entry-field') - this.confirmationMessagePopover = page.locator('div.euiPopover__panel .euiText') - this.streamGroupId = page.locator('.streamItemId[data-testid^="stream-group-id"]').first() + this.confirmationMessagePopover = page.locator( + 'div.euiPopover__panel .euiText', + ) + this.streamGroupId = page + .locator('.streamItemId[data-testid^="stream-group-id"]') + .first() this.streamGroupName = page.getByTestId('stream-group-name') - this.streamMessage = page.locator('[data-testid*="-date"][data-testid^="stream-message"]') + this.streamMessage = page.locator( + '[data-testid*="-date"][data-testid^="stream-message"]', + ) this.streamConsumerName = page.getByTestId('stream-consumer-') this.consumerGroup = page.getByTestId('stream-group-') this.entryIdInfoIcon = page.getByTestId('entry-id-info-icon') @@ -506,7 +605,9 @@ export class BrowserPage extends BasePage { // CHECKBOXES this.showTtlCheckbox = page.getByTestId('test-check-ttl').locator('..') this.showTtlColumnCheckbox = page.getByTestId('show-ttl').locator('..') - this.showSizeColumnCheckbox = page.getByTestId('show-key-size').locator('..') + this.showSizeColumnCheckbox = page + .getByTestId('show-key-size') + .locator('..') // UTILITY FUNCTIONS this.getHashTtlFieldInput = (fieldName: string): Locator => @@ -520,12 +621,14 @@ export class BrowserPage extends BasePage { } async commonAddNewKey(keyName: string, TTL?: string): Promise { - await this.waitForLocatorNotVisible(this.progressLine) await this.waitForLocatorNotVisible(this.loader) await this.plusAddKeyButton.click() await this.addKeyNameInput.click() - await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) if (TTL !== undefined) { await this.keyTTLInput.click() await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) @@ -533,29 +636,47 @@ export class BrowserPage extends BasePage { await this.keyTypeDropDown.click() } - async addStringKey(keyName: string, value = ' ', TTL?: string): Promise { + async addStringKey( + keyName: string, + value = ' ', + TTL?: string, + ): Promise { await this.plusAddKeyButton.click() await this.keyTypeDropDown.click() await this.stringOption.click() await this.addKeyNameInput.click() - await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) if (TTL !== undefined) { await this.keyTTLInput.click() await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) } await this.stringKeyValueInput.click() - await this.stringKeyValueInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.stringKeyValueInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) await this.addKeyButton.click() } - async addJsonKey(keyName: string, value: string, TTL?: string): Promise { + async addJsonKey( + keyName: string, + value: string, + TTL?: string, + ): Promise { await this.plusAddKeyButton.click() await this.keyTypeDropDown.click() await this.jsonOption.click() await this.addKeyNameInput.click() - await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + }) await this.jsonKeyValueInput.click() - await this.jsonKeyValueInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.jsonKeyValueInput.fill(value, { + timeout: 0, + }) if (TTL !== undefined) { await this.keyTTLInput.click() await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) @@ -573,36 +694,65 @@ export class BrowserPage extends BasePage { await this.keyTypeDropDown.click() await this.setOption.click() await this.addKeyNameInput.click() - await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) await this.keyTTLInput.click() await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) - await this.setMemberInput.fill(members, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(members, { + timeout: 0, + noWaitAfter: false, + }) await this.addKeyButton.click() + await this.toast.closeToast() } - async addZSetKey(keyName: string, scores = ' ', TTL = ' ', members = ' '): Promise { + async addZSetKey( + keyName: string, + scores = ' ', + TTL = ' ', + members = ' ', + ): Promise { await this.waitForLocatorNotVisible(this.progressLine) await this.waitForLocatorNotVisible(this.loader) await this.plusAddKeyButton.click() await this.keyTypeDropDown.click() await this.zsetOption.click() await this.addKeyNameInput.click() - await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) await this.keyTTLInput.click() await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) - await this.setMemberInput.fill(members, { timeout: 0, noWaitAfter: false }) - await this.zsetMemberScoreInput.fill(scores, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(members, { + timeout: 0, + noWaitAfter: false, + }) + await this.zsetMemberScoreInput.fill(scores, { + timeout: 0, + noWaitAfter: false, + }) await this.addKeyButton.click() } - async addListKey(keyName: string, TTL = ' ', element: string[] = [' '], position: AddElementInList = AddElementInList.Tail): Promise { + async addListKey( + keyName: string, + TTL = ' ', + element: string[] = [' '], + position: AddElementInList = AddElementInList.Tail, + ): Promise { await this.waitForLocatorNotVisible(this.progressLine) await this.waitForLocatorNotVisible(this.loader) await this.plusAddKeyButton.click() await this.keyTypeDropDown.click() await this.listOption.click() await this.addKeyNameInput.click() - await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) await this.keyTTLInput.click() await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) if (position === AddElementInList.Head) { @@ -610,9 +760,12 @@ export class BrowserPage extends BasePage { await this.removeFromHeadSelection.click() await expect(this.removeFromHeadSelection).not.toBeVisible() } - for (let i = 0; i < element.length; i++) { + for (let i = 0; i < element.length; i += 1) { await this.getListElementInput(i).click() - await this.getListElementInput(i).fill(element[i], { timeout: 0, noWaitAfter: false }) + await this.getListElementInput(i).fill(element[i], { + timeout: 0, + noWaitAfter: false, + }) if (element.length > 1 && i < element.length - 1) { await this.addAdditionalElement.click() } @@ -620,7 +773,13 @@ export class BrowserPage extends BasePage { await this.addKeyButton.click() } - async addHashKey(keyName: string, TTL = ' ', field = ' ', value = ' ', fieldTtl = ''): Promise { + async addHashKey( + keyName: string, + TTL = ' ', + field = ' ', + value = ' ', + fieldTtl = '', + ): Promise { if (await this.toast.isCloseButtonVisible()) { await this.toast.closeToast() } @@ -630,18 +789,36 @@ export class BrowserPage extends BasePage { await this.keyTypeDropDown.click() await this.hashOption.click() await this.addKeyNameInput.click() - await this.addKeyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) await this.keyTTLInput.click() await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) - await this.hashFieldNameInput.fill(field, { timeout: 0, noWaitAfter: false }) - await this.hashFieldValueInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.hashFieldNameInput.fill(field, { + timeout: 0, + noWaitAfter: false, + }) + await this.hashFieldValueInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) if (fieldTtl !== '') { - await this.hashTtlFieldInput.fill(fieldTtl, { timeout: 0, noWaitAfter: false }) + await this.hashTtlFieldInput.fill(fieldTtl, { + timeout: 0, + noWaitAfter: false, + }) } await this.addKeyButton.click() + await this.toast.closeToast() } - async addStreamKey(keyName: string, field: string, value: string, TTL?: string): Promise { + async addStreamKey( + keyName: string, + field: string, + value: string, + TTL?: string, + ): Promise { await this.commonAddNewKey(keyName, TTL) await this.streamOption.click() await expect(this.streamEntryId).toHaveValue('*', { timeout: 5000 }) @@ -652,34 +829,52 @@ export class BrowserPage extends BasePage { await this.toast.closeToast() } - async addEntryToStream(field: string, value: string, entryId?: string): Promise { + async addEntryToStream( + field: string, + value: string, + entryId?: string, + ): Promise { await this.addNewStreamEntry.click() await this.streamField.fill(field, { timeout: 0, noWaitAfter: false }) await this.streamValue.fill(value, { timeout: 0, noWaitAfter: false }) if (entryId !== undefined) { - await this.streamEntryId.fill(entryId, { timeout: 0, noWaitAfter: false }) + await this.streamEntryId.fill(entryId, { + timeout: 0, + noWaitAfter: false, + }) } await this.saveElementButton.click() await expect(this.streamEntriesContainer).toContainText(field) await expect(this.streamEntriesContainer).toContainText(value) } - async fulfillSeveralStreamFields(fields: string[], values: string[], entryId?: string): Promise { - for (let i = 0; i < fields.length; i++) { - await this.streamField.nth(-1).fill(fields[i], { timeout: 0, noWaitAfter: false }) - await this.streamValue.nth(-1).fill(values[i], { timeout: 0, noWaitAfter: false }) + async fulfillSeveralStreamFields( + fields: string[], + values: string[], + entryId?: string, + ): Promise { + for (let i = 0; i < fields.length; i += 1) { + await this.streamField + .nth(-1) + .fill(fields[i], { timeout: 0, noWaitAfter: false }) + await this.streamValue + .nth(-1) + .fill(values[i], { timeout: 0, noWaitAfter: false }) if (i < fields.length - 1) { await this.addAdditionalElement.click() } } if (entryId !== undefined) { - await this.streamEntryId.fill(entryId, { timeout: 0, noWaitAfter: false }) + await this.streamEntryId.fill(entryId, { + timeout: 0, + noWaitAfter: false, + }) } } async selectFilterGroupType(groupName: string): Promise { await this.filterByKeyTypeDropDown.click() - await this.filterOptionType.withText(groupName).click() + await this.filterOptionType.locator(groupName).click() } async setAllKeyType(): Promise { @@ -689,7 +884,10 @@ export class BrowserPage extends BasePage { async searchByKeyName(keyName: string): Promise { await this.filterByPatterSearchInput.click() - await this.filterByPatterSearchInput.fill(keyName, { timeout: 0, noWaitAfter: false }) + await this.filterByPatterSearchInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) await this.page.keyboard.press('Enter') } @@ -700,7 +898,7 @@ export class BrowserPage extends BasePage { async isKeyIsDisplayedInTheList(keyName: string): Promise { const keyNameInTheList = this.getKeySelectorByName(keyName) await this.waitForLocatorNotVisible(this.loader) - return await keyNameInTheList.isVisible() + return keyNameInTheList.isVisible() } async deleteKey(): Promise { @@ -729,39 +927,60 @@ export class BrowserPage extends BasePage { async deleteKeyByNameFromList(keyName: string): Promise { await this.searchByKeyName(keyName) await this.keyNameInTheList.hover() - await this.page.locator(`[data-testid="delete-key-btn-${keyName}"]`).click() + await this.page + .locator(`[data-testid="delete-key-btn-${keyName}"]`) + .click() await this.submitDeleteKeyButton.click() } async editKeyName(keyName: string): Promise { await this.editKeyNameButton.click() - await this.keyNameInput.fill(keyName, { timeout: 0, noWaitAfter: false }) - await this.EditorButton.applyBtn.click() + await this.keyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() } async editStringKeyValue(value: string): Promise { await this.stringKeyValueInput.click() - await this.stringKeyValueInput.fill(value, { timeout: 0, noWaitAfter: false }) - await this.EditorButton.applyBtn.click() + await this.stringKeyValueInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() } - async getStringKeyValue(): Promise { - return await this.stringKeyValueInput.textContent() + async getStringKeyValue(): Promise { + return this.stringKeyValueInput.textContent() } - async getZsetKeyScore(): Promise { - return await this.zsetScoresList.textContent() + async getZsetKeyScore(): Promise { + return this.zsetScoresList.textContent() } - async addFieldToHash(keyFieldValue: string, keyValue: string, fieldTtl = ''): Promise { + async addFieldToHash( + keyFieldValue: string, + keyValue: string, + fieldTtl = '', + ): Promise { if (await this.toast.toastCloseButton.isVisible()) { await this.toast.toastCloseButton.click() } await this.addKeyValueItemsButton.click() - await this.hashFieldInput.fill(keyFieldValue, { timeout: 0, noWaitAfter: false }) - await this.hashValueInput.fill(keyValue, { timeout: 0, noWaitAfter: false }) + await this.hashFieldInput.fill(keyFieldValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.hashValueInput.fill(keyValue, { + timeout: 0, + noWaitAfter: false, + }) if (fieldTtl !== '') { - await this.hashTtlFieldInput.fill(fieldTtl, { timeout: 0, noWaitAfter: false }) + await this.hashTtlFieldInput.fill(fieldTtl, { + timeout: 0, + noWaitAfter: false, + }) } await this.saveHashFieldButton.click() } @@ -769,34 +988,46 @@ export class BrowserPage extends BasePage { async editHashKeyValue(value: string): Promise { await this.hashFieldValue.hover() await this.editHashButton.click() - await this.hashFieldValueEditor.fill(value, { timeout: 0, noWaitAfter: false }) - await this.EditorButton.applyBtn.click() + await this.hashFieldValueEditor.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() } - async editHashFieldTtlValue(fieldName: string, fieldTtl: string): Promise { + async editHashFieldTtlValue( + fieldName: string, + fieldTtl: string, + ): Promise { await this.getHashTtlFieldInput(fieldName).hover() await this.editHashFieldTtlButton.click() - await this.inlineItemEditor.fill(fieldTtl, { timeout: 0, noWaitAfter: false }) + await this.inlineItemEditor.fill(fieldTtl, { + timeout: 0, + noWaitAfter: false, + }) await this.applyButton.click() } - async getHashKeyValue(): Promise { - return await this.hashFieldValue.textContent() + async getHashKeyValue(): Promise { + return this.hashFieldValue.textContent() } async editListKeyValue(value: string): Promise { await this.listElementsList.hover() await this.editListButton.click() - await this.listKeyElementEditorInput.fill(value, { timeout: 0, noWaitAfter: false }) - await this.EditorButton.applyBtn.click() + await this.listKeyElementEditorInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() } - async getListKeyValue(): Promise { - return await this.listElementsList.textContent() + async getListKeyValue(): Promise { + return this.listElementsList.textContent() } - async getJsonKeyValue(): Promise { - return await this.jsonKeyValue.textContent() + async getJsonKeyValue(): Promise { + return this.jsonKeyValue.textContent() } async searchByTheValueInKeyDetails(value: string): Promise { @@ -821,7 +1052,10 @@ export class BrowserPage extends BasePage { await this.toast.toastCloseButton.click() } await this.addKeyValueItemsButton.click() - await this.setMemberInput.fill(keyMember, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(keyMember, { + timeout: 0, + noWaitAfter: false, + }) await this.saveMemberButton.click() } @@ -830,8 +1064,14 @@ export class BrowserPage extends BasePage { await this.toast.toastCloseButton.click() } await this.addKeyValueItemsButton.click() - await this.setMemberInput.fill(keyMember, { timeout: 0, noWaitAfter: false }) - await this.zsetMemberScoreInput.fill(score, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(keyMember, { + timeout: 0, + noWaitAfter: false, + }) + await this.zsetMemberScoreInput.fill(score, { + timeout: 0, + noWaitAfter: false, + }) await this.saveMemberButton.click() } @@ -841,11 +1081,16 @@ export class BrowserPage extends BasePage { } async openKeyDetailsByKeyName(keyName: string): Promise { - const keyNameInTheList = this.page.locator(`[data-testid="key-${keyName}"]`) + const keyNameInTheList = this.page.locator( + `[data-testid="key-${keyName}"]`, + ) await keyNameInTheList.click() } - async addElementToList(element: string[], position: AddElementInList = AddElementInList.Tail): Promise { + async addElementToList( + element: string[], + position: AddElementInList = AddElementInList.Tail, + ): Promise { if (await this.toast.toastCloseButton.isVisible()) { await this.toast.toastCloseButton.click() } @@ -855,9 +1100,12 @@ export class BrowserPage extends BasePage { await this.removeFromHeadSelection.click() await expect(this.removeFromHeadSelection).not.toBeVisible() } - for (let i = 0; i < element.length; i++) { + for (let i = 0; i < element.length; i += 1) { await this.getListElementInput(i).click() - await this.getListElementInput(i).fill(element[i], { timeout: 0, noWaitAfter: false }) + await this.getListElementInput(i).fill(element[i], { + timeout: 0, + noWaitAfter: false, + }) if (element.length > 1 && i < element.length - 1) { await this.addAdditionalElement.click() } @@ -867,7 +1115,9 @@ export class BrowserPage extends BasePage { async removeListElementFromHeadOld(): Promise { await this.removeElementFromListIconButton.click() - await expect(await this.countInput.getAttribute('disabled')).toBeTruthy() + await expect( + await this.countInput.getAttribute('disabled'), + ).toBeTruthy() await this.removeElementFromListSelect.click() await this.removeFromHeadSelection.click() await this.removeElementFromListButton.click() @@ -890,25 +1140,46 @@ export class BrowserPage extends BasePage { await this.confirmRemoveListElementButton.click() } - async addJsonKeyOnTheSameLevel(jsonKey: string, jsonKeyValue: string): Promise { + async addJsonKeyOnTheSameLevel( + jsonKey: string, + jsonKeyValue: string, + ): Promise { await this.addJsonObjectButton.click() - await this.jsonKeyInput.fill(jsonKey, { timeout: 0, noWaitAfter: false }) - await this.jsonValueInput.fill(jsonKeyValue, { timeout: 0, noWaitAfter: false }) - await this.EditorButton.applyBtn.click() + await this.jsonKeyInput.fill(jsonKey, { + timeout: 0, + noWaitAfter: false, + }) + await this.jsonValueInput.fill(jsonKeyValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() } - async addJsonKeyInsideStructure(jsonKey: string, jsonKeyValue: string): Promise { + async addJsonKeyInsideStructure( + jsonKey: string, + jsonKeyValue: string, + ): Promise { await this.expandJsonObject.click() await this.addJsonFieldButton.click() - await this.jsonKeyInput.fill(jsonKey, { timeout: 0, noWaitAfter: false }) - await this.jsonValueInput.fill(jsonKeyValue, { timeout: 0, noWaitAfter: false }) - await this.EditorButton.applyBtn.click() + await this.jsonKeyInput.fill(jsonKey, { + timeout: 0, + noWaitAfter: false, + }) + await this.jsonValueInput.fill(jsonKeyValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() } async addJsonValueInsideStructure(jsonKeyValue: string): Promise { await this.expandJsonObject.click() await this.addJsonFieldButton.click() - await this.jsonValueInput.fill(jsonKeyValue, { timeout: 0, noWaitAfter: false }) + await this.jsonValueInput.fill(jsonKeyValue, { + timeout: 0, + noWaitAfter: false, + }) await this.applyButton.click() } @@ -917,7 +1188,10 @@ export class BrowserPage extends BasePage { await this.expandJsonObject.click() } await this.editJsonObjectButton.click() - await this.jsonValueInput.fill(jsonStructure, { timeout: 0, noWaitAfter: false }) + await this.jsonValueInput.fill(jsonStructure, { + timeout: 0, + noWaitAfter: false, + }) await this.applyEditButton.click() } @@ -928,15 +1202,21 @@ export class BrowserPage extends BasePage { async getKeyLength(): Promise { const rawValue = await this.keyLengthDetails.textContent() - const parts = rawValue.split(' ') + const parts = (rawValue ?? '').split(' ') return parts[parts.length - 1] } async createConsumerGroup(groupName: string, id?: string): Promise { await this.addKeyValueItemsButton.click() - await this.groupNameInput.fill(groupName, { timeout: 0, noWaitAfter: false }) + await this.groupNameInput.fill(groupName, { + timeout: 0, + noWaitAfter: false, + }) if (id !== undefined) { - await this.consumerIdInput.fill(id, { timeout: 0, noWaitAfter: false }) + await this.consumerIdInput.fill(id, { + timeout: 0, + noWaitAfter: false, + }) } await this.saveGroupsButton.click() } @@ -949,26 +1229,42 @@ export class BrowserPage extends BasePage { } async selectFormatter(formatter: string): Promise { - const option = this.page.locator(`[data-test-subj="format-option-${formatter}"]`) + const option = this.page.locator( + `[data-test-subj="format-option-${formatter}"]`, + ) await this.formatSwitcher.click() await option.click() } async verifyScannningMore(): Promise { for (let i = 10; i < 100; i += 10) { - const rememberedScanResults = Number((await this.keysNumberOfResults.textContent()).replace(/\s/g, '')) - await expect(this.progressKeyList).not.toBeVisible({ timeout: 30000 }) + const rememberedScanResults = Number( + (await this.keysNumberOfResults.textContent())?.replace( + /\s/g, + '', + ), + ) + await expect(this.progressKeyList).not.toBeVisible({ + timeout: 30000, + }) const scannedValueText = await this.scannedValue.textContent() const regExp = new RegExp(`${i} ...`) await expect(scannedValueText).toMatch(regExp) await this.scanMoreButton.click() - const scannedResults = Number((await this.keysNumberOfResults.textContent()).replace(/\s/g, '')) + const scannedResults = Number( + (await this.keysNumberOfResults.textContent())?.replace( + /\s/g, + '', + ), + ) await expect(scannedResults).toBeGreaterThan(rememberedScanResults) } } async selectIndexByName(index: string): Promise { - const option = this.page.locator(`[data-test-subj="mode-option-type-${index}"]`) + const option = this.page.locator( + `[data-test-subj="mode-option-type-${index}"]`, + ) await this.selectIndexDdn.click() await option.click() } @@ -983,7 +1279,7 @@ export class BrowserPage extends BasePage { } async clickGuideLinksByName(guide: string): Promise { - const linkGuide = this.page.locator('[data-testid^="guide-button-"]').withText(guide) + const linkGuide = this.page.locator(guide) await linkGuide.click() } } diff --git a/tests/playwright/pageObjects/components/common/toast.ts b/tests/playwright/pageObjects/components/common/toast.ts index 65bdfc313c..d90e5731a4 100644 --- a/tests/playwright/pageObjects/components/common/toast.ts +++ b/tests/playwright/pageObjects/components/common/toast.ts @@ -1,16 +1,21 @@ -import {Locator, Page} from '@playwright/test' -import {BasePage} from '../../base-page' -import {ToastSelectors} from '../../../selectors' +import { Locator, Page } from '@playwright/test' +import { BasePage } from '../../base-page' +import { ToastSelectors } from '../../../selectors' -export class Toast extends BasePage{ +export class Toast extends BasePage { + public readonly toastHeader: Locator - private readonly toastHeader: Locator - private readonly toastBody: Locator - private readonly toastSuccess: Locator - private readonly toastError: Locator - private readonly toastCloseButton: Locator - private readonly toastSubmitBtn: Locator - private readonly toastCancelBtn: Locator + public readonly toastBody: Locator + + public readonly toastSuccess: Locator + + public readonly toastError: Locator + + public readonly toastCloseButton: Locator + + public readonly toastSubmitBtn: Locator + + public readonly toastCancelBtn: Locator constructor(page: Page) { super(page) @@ -23,7 +28,6 @@ export class Toast extends BasePage{ this.toastCancelBtn = page.getByTestId(ToastSelectors.toastCancelBtn) } - async isCloseButtonVisible(): Promise { return this.isVisible(ToastSelectors.toastCloseButton) } @@ -31,8 +35,4 @@ export class Toast extends BasePage{ async closeToast(): Promise { await this.toastCloseButton.click() } - - async getNotificationMessage(): Promise { - return this.toastHeader.textContent() - } } diff --git a/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts index 97173299d2..8112215bdf 100644 --- a/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts +++ b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts @@ -1,14 +1,20 @@ import { Locator, Page } from '@playwright/test' -import {BasePage} from "../base-page"; +import { BasePage } from '../base-page' export class RedisCloudSigninPanel extends BasePage { - private readonly ssoOauthButton: Locator - private readonly ssoEmailInput: Locator - private readonly submitBtn: Locator - private readonly oauthAgreement: Locator - private readonly googleOauth: Locator - private readonly githubOauth: Locator - private readonly ssoOauth: Locator + readonly ssoOauthButton: Locator + + readonly ssoEmailInput: Locator + + readonly submitBtn: Locator + + readonly oauthAgreement: Locator + + readonly googleOauth: Locator + + readonly githubOauth: Locator + + readonly ssoOauth: Locator constructor(page: Page) { super(page) diff --git a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts index 2426f67194..542f3c704c 100755 --- a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts @@ -1,9 +1,7 @@ import { Page, Locator } from '@playwright/test' -import {BasePage} from "../base-page" - -export class AddRdiInstanceDialog extends BasePage{ - readonly page: Page +import { BasePage } from '../base-page' +export class AddRdiInstanceDialog extends BasePage { // INPUTS readonly rdiAliasInput: Locator @@ -36,7 +34,9 @@ export class AddRdiInstanceDialog extends BasePage{ this.passwordInput = page.getByTestId('connection-form-password-input') this.addInstanceButton = page.getByTestId('connection-form-add-button') - this.cancelInstanceBtn = page.getByTestId('connection-form-cancel-button') + this.cancelInstanceBtn = page.getByTestId( + 'connection-form-cancel-button', + ) this.connectToRdiForm = page.getByTestId('connection-form') // Assuming that the two-level parent traversal is needed. @@ -52,12 +52,3 @@ export class AddRdiInstanceDialog extends BasePage{ .locator('xpath=ancestor::div[2]//svg') } } - -export type RdiInstance = { - alias: string; - url: string; - version?: string; - lastConnection?: string; - username?: string; - password?: string; -} diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts index df3404e58e..95256a2cdf 100644 --- a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts @@ -1,92 +1,94 @@ import { expect, Locator, Page } from '@playwright/test' import { TlsCertificates } from '../../helpers/constants' import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel' -import {SentinelParameters, AddNewDatabaseParameters, SSHParameters } from '../../types' -import {BasePage} from '../base-page' +import { + SentinelParameters, + AddNewDatabaseParameters, + SSHParameters, +} from '../../types' +import { BasePage } from '../base-page' - -export class AddRedisDatabaseDialog extends BasePage{ - private readonly page: Page - - private readonly redisCloudSigninPanel: RedisCloudSigninPanel +export class AddRedisDatabaseDialog extends BasePage { + readonly redisCloudSigninPanel: RedisCloudSigninPanel // BUTTONS - private readonly addDatabaseButton: Locator + readonly addDatabaseButton: Locator readonly addRedisDatabaseButton: Locator - private readonly customSettingsButton: Locator + readonly customSettingsButton: Locator - private readonly addAutoDiscoverDatabase: Locator + readonly addAutoDiscoverDatabase: Locator - private readonly addCloudDatabaseButton: Locator + readonly addCloudDatabaseButton: Locator - private readonly redisSoftwareButton: Locator + readonly redisSoftwareButton: Locator - private readonly redisSentinelButton: Locator + readonly redisSentinelButton: Locator // TEXT INPUTS - private readonly hostInput: Locator + readonly hostInput: Locator - private readonly portInput: Locator + readonly portInput: Locator - private readonly databaseAliasInput: Locator + readonly databaseAliasInput: Locator - private readonly passwordInput: Locator + readonly passwordInput: Locator - private readonly usernameInput: Locator + readonly usernameInput: Locator - private readonly accessKeyInput: Locator + readonly accessKeyInput: Locator - private readonly secretKeyInput: Locator + readonly secretKeyInput: Locator - private readonly databaseIndexInput: Locator + readonly databaseIndexInput: Locator // TABS - private readonly generalTab: Locator + readonly generalTab: Locator - private readonly securityTab: Locator + readonly securityTab: Locator - private readonly decompressionTab: Locator + readonly decompressionTab: Locator // DROPDOWNS - private readonly caCertField: Locator + readonly caCertField: Locator - private readonly clientCertField: Locator + readonly clientCertField: Locator - private readonly selectCompressor: Locator + readonly selectCompressor: Locator // CHECKBOXES - private readonly databaseIndexCheckbox: Locator + readonly databaseIndexCheckbox: Locator - private readonly useSSHCheckbox: Locator + readonly useSSHCheckbox: Locator // RADIO BUTTONS - private readonly sshPasswordRadioBtn: Locator + readonly sshPasswordRadioBtn: Locator - private readonly sshPrivateKeyRadioBtn: Locator + readonly sshPrivateKeyRadioBtn: Locator // LABELS - private readonly dataCompressorLabel: Locator + readonly dataCompressorLabel: Locator // SSH TEXT INPUTS - private readonly sshHostInput: Locator + readonly sshHostInput: Locator - private readonly sshPortInput: Locator + readonly sshPortInput: Locator - private readonly sshUsernameInput: Locator + readonly sshUsernameInput: Locator - private readonly sshPasswordInput: Locator + readonly sshPasswordInput: Locator - private readonly sshPrivateKeyInput: Locator + readonly sshPrivateKeyInput: Locator - private readonly sshPassphraseInput: Locator + readonly sshPassphraseInput: Locator // OTHER - private readonly timeoutInput: Locator + readonly timeoutInput: Locator // For certificate removal aiChatMessage: Locator + aiCloseMessage: Locator trashIconMsk(certificate: TlsCertificates): string { @@ -98,16 +100,19 @@ export class AddRedisDatabaseDialog extends BasePage{ } constructor(page: Page) { - super(page) this.page = page this.redisCloudSigninPanel = new RedisCloudSigninPanel(page) // BUTTONS - this.addDatabaseButton = page.locator('[data-testid^="add-redis-database"]') + this.addDatabaseButton = page.locator( + '[data-testid^="add-redis-database"]', + ) this.addRedisDatabaseButton = page.getByTestId('btn-submit') this.customSettingsButton = page.getByTestId('btn-connection-settings') - this.addAutoDiscoverDatabase = page.getByTestId('add-database_tab_software') + this.addAutoDiscoverDatabase = page.getByTestId( + 'add-database_tab_software', + ) this.addCloudDatabaseButton = page.getByTestId('create-free-db-btn') this.redisSoftwareButton = page.getByTestId('option-btn-software') this.redisSentinelButton = page.getByTestId('option-btn-sentinel') @@ -125,7 +130,9 @@ export class AddRedisDatabaseDialog extends BasePage{ // TABS this.generalTab = page.getByTestId('manual-form-tab-general') this.securityTab = page.getByTestId('manual-form-tab-security') - this.decompressionTab = page.getByTestId('manual-form-tab-decompression') + this.decompressionTab = page.getByTestId( + 'manual-form-tab-decompression', + ) // DROPDOWNS this.caCertField = page.getByTestId('select-ca-cert') @@ -133,7 +140,9 @@ export class AddRedisDatabaseDialog extends BasePage{ this.selectCompressor = page.getByTestId('select-compressor') // CHECKBOXES - this.databaseIndexCheckbox = page.locator('[data-testid="showDb"] ~ div') + this.databaseIndexCheckbox = page.locator( + '[data-testid="showDb"] ~ div', + ) this.useSSHCheckbox = page.locator('[data-testid="use-ssh"] ~ div') // RADIO BUTTONS @@ -141,10 +150,13 @@ export class AddRedisDatabaseDialog extends BasePage{ this.sshPrivateKeyRadioBtn = page.locator('#privateKey ~ div') // LABELS - this.dataCompressorLabel = page.getByTestId('[data-testid="showCompressor"] ~ label') + this.dataCompressorLabel = page.getByTestId( + '[data-testid="showCompressor"] ~ label', + ) this.aiChatMessage = page.getByTestId('ai-chat-message-btn') - this.aiCloseMessage = page.locator('[aria-label="Closes this modal window"]') - + this.aiCloseMessage = page.locator( + '[aria-label="Closes this modal window"]', + ) // SSH TEXT INPUTS this.sshHostInput = page.getByTestId('sshHost') @@ -158,7 +170,9 @@ export class AddRedisDatabaseDialog extends BasePage{ this.timeoutInput = page.getByTestId('timeout') } - async addRedisDataBase(parameters: AddNewDatabaseParameters): Promise { + async addRedisDataBase( + parameters: AddNewDatabaseParameters, + ): Promise { await expect(this.addDatabaseButton).toBeVisible({ timeout: 10000 }) await this.addDatabaseButton.click() await this.customSettingsButton.click() @@ -173,7 +187,10 @@ export class AddRedisDatabaseDialog extends BasePage{ } } - async addLogicalRedisDatabase(parameters: AddNewDatabaseParameters, index: string): Promise { + async addLogicalRedisDatabase( + parameters: AddNewDatabaseParameters, + index: string, + ): Promise { await this.addDatabaseButton.click() await this.customSettingsButton.click() await this.hostInput.fill(parameters.host) @@ -192,13 +209,15 @@ export class AddRedisDatabaseDialog extends BasePage{ async addStandaloneSSHDatabase( databaseParameters: AddNewDatabaseParameters, - sshParameters: SSHParameters + sshParameters: SSHParameters, ): Promise { await this.addDatabaseButton.click() await this.customSettingsButton.click() await this.hostInput.fill(databaseParameters.host) await this.portInput.fill(databaseParameters.port) - await this.databaseAliasInput.fill(databaseParameters.databaseName || '') + await this.databaseAliasInput.fill( + databaseParameters.databaseName || '', + ) if (databaseParameters.databaseUsername) { await this.usernameInput.fill(databaseParameters.databaseUsername) } @@ -226,7 +245,9 @@ export class AddRedisDatabaseDialog extends BasePage{ await this.addRedisDatabaseButton.click() } - async discoverSentinelDatabases(parameters: SentinelParameters): Promise { + async discoverSentinelDatabases( + parameters: SentinelParameters, + ): Promise { await this.addDatabaseButton.click() await this.redisSentinelButton.click() if (parameters.sentinelHost) { @@ -240,7 +261,9 @@ export class AddRedisDatabaseDialog extends BasePage{ } } - async addAutodiscoverREClusterDatabase(parameters: AddNewDatabaseParameters): Promise { + async addAutodiscoverREClusterDatabase( + parameters: AddNewDatabaseParameters, + ): Promise { await this.addDatabaseButton.click() await this.redisSoftwareButton.click() await this.hostInput.fill(parameters.host) @@ -249,14 +272,19 @@ export class AddRedisDatabaseDialog extends BasePage{ await this.passwordInput.fill(parameters.databasePassword || '') } - async addAutodiscoverRECloudDatabase(cloudAPIAccessKey: string, cloudAPISecretKey: string): Promise { + async addAutodiscoverRECloudDatabase( + cloudAPIAccessKey: string, + cloudAPISecretKey: string, + ): Promise { await this.addDatabaseButton.click() await this.addCloudDatabaseButton.click() await this.accessKeyInput.fill(cloudAPIAccessKey) await this.secretKeyInput.fill(cloudAPISecretKey) } - async addOssClusterDatabase(parameters: AddNewDatabaseParameters): Promise { + async addOssClusterDatabase( + parameters: AddNewDatabaseParameters, + ): Promise { await this.addDatabaseButton.click() await this.customSettingsButton.click() if (parameters.ossClusterHost) { @@ -266,7 +294,9 @@ export class AddRedisDatabaseDialog extends BasePage{ await this.portInput.fill(parameters.ossClusterPort) } if (parameters.ossClusterDatabaseName) { - await this.databaseAliasInput.fill(parameters.ossClusterDatabaseName) + await this.databaseAliasInput.fill( + parameters.ossClusterDatabaseName, + ) } } @@ -278,10 +308,18 @@ export class AddRedisDatabaseDialog extends BasePage{ await this.page.locator(`[id="${compressor}"]`).click() } - async removeCertificateButton(certificate: TlsCertificates, name: string): Promise { + async removeCertificateButton( + certificate: TlsCertificates, + name: string, + ): Promise { await this.securityTab.click() - const row = this.page.locator('button').locator('div').filter({ hasText: name }) - const removeButtonFooter = this.page.locator('[class^="_popoverFooter"]') + const row = this.page + .locator('button') + .locator('div') + .filter({ hasText: name }) + const removeButtonFooter = this.page.locator( + '[class^="_popoverFooter"]', + ) if (certificate === TlsCertificates.CA) { await this.caCertField.click() } else { diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts index 427cc8ba8f..1a21adb0b8 100644 --- a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -1,41 +1,57 @@ import { expect, Locator, Page } from '@playwright/test' -import {BasePage} from '../base-page' + +import { BasePage } from '../base-page' import { UserAgreementSelectors } from '../../selectors' -import log from 'node-color-log' export class UserAgreementDialog extends BasePage { + readonly userAgreementsPopup: Locator + + readonly submitButton: Locator + + readonly switchOptionEula: Locator - // Private selectors - private readonly userAgreementsPopup: Locator - private readonly submitButton: Locator - private readonly switchOptionEula: Locator - private readonly switchOptionEncryption: Locator - private readonly pluginSectionWithText: Locator - private readonly recommendedSwitcher: Locator + readonly switchOptionEncryption: Locator + + readonly pluginSectionWithText: Locator + + readonly recommendedSwitcher: Locator constructor(page: Page) { super(page) - this.userAgreementsPopup = page.getByTestId(UserAgreementSelectors.userAgreementsPopup) - this.submitButton = page.getByTestId(UserAgreementSelectors.submitButton) - this.switchOptionEula = page.getByTestId(UserAgreementSelectors.switchOptionEula) - this.switchOptionEncryption = page.getByTestId(UserAgreementSelectors.switchOptionEncryption) - this.pluginSectionWithText = page.getByTestId(UserAgreementSelectors.pluginSectionWithText) - this.recommendedSwitcher = page.getByTestId(UserAgreementSelectors.recommendedSwitcher) + this.userAgreementsPopup = page.getByTestId( + UserAgreementSelectors.userAgreementsPopup, + ) + this.submitButton = page.getByTestId( + UserAgreementSelectors.submitButton, + ) + this.switchOptionEula = page.getByTestId( + UserAgreementSelectors.switchOptionEula, + ) + this.switchOptionEncryption = page.getByTestId( + UserAgreementSelectors.switchOptionEncryption, + ) + this.pluginSectionWithText = page.getByTestId( + UserAgreementSelectors.pluginSectionWithText, + ) + this.recommendedSwitcher = page.getByTestId( + UserAgreementSelectors.recommendedSwitcher, + ) } async acceptLicenseTerms(): Promise { - try { - await this.switchOptionEula.waitFor({timeout: 3000}) // because the state isn't clear - }catch (error) { - log.info("💩 Failed on waiting user agreement dialog ") + await this.switchOptionEula.waitFor({ timeout: 3000 }) // because the state isn't clear + } catch (error) { + // Ignore error if the dialog is not visible } if (await this.switchOptionEula.isVisible()) { await this.recommendedSwitcher.click() await this.switchOptionEula.click() await this.submitButton.click() - await expect(this.userAgreementsPopup).not.toBeVisible({ timeout: 2000 }) + await expect(this.userAgreementsPopup).not.toBeVisible({ + timeout: 2000, + }) } } @@ -44,6 +60,6 @@ export class UserAgreementDialog extends BasePage { } async isUserAgreementDialogVisible(): Promise { - return await this.userAgreementsPopup.isVisible() + return this.userAgreementsPopup.isVisible() } } diff --git a/tests/playwright/pageObjects/index.ts b/tests/playwright/pageObjects/index.ts index f9d789d45f..85b64cac53 100644 --- a/tests/playwright/pageObjects/index.ts +++ b/tests/playwright/pageObjects/index.ts @@ -5,7 +5,6 @@ export * from './dialogs/add-redis-database-dialog' export * from './dialogs/user-agreement-dialog' export * from './base-overview-page' export * from './browser-page' -export * from './my-redis-databases-page' export * from './rdi-instances-list-page' export * from './auto-discover-redis-enterprise-databases' export * from './base-page' diff --git a/tests/playwright/pageObjects/my-redis-databases-page.ts b/tests/playwright/pageObjects/my-redis-databases-page.ts deleted file mode 100755 index 4b1198912b..0000000000 --- a/tests/playwright/pageObjects/my-redis-databases-page.ts +++ /dev/null @@ -1,328 +0,0 @@ -import { expect, Locator, Page } from '@playwright/test' -// import { InsightsPanel } from './components/insights-panel' -import { BaseOverviewPage } from './base-overview-page' -import { Toast } from './components/common/toast' -// import { NavigationPanel } from './components/navigation-panel' -// import { NavigationHeader } from './components/navigation/navigation-header' -// import { AuthorizationDialog } from './dialogs/authorization-dialog' -import { AddRedisDatabaseDialog } from './dialogs/add-redis-database-dialog' -import {DatabaseAPIRequests} from '../helpers/api/api-databases'; -// import { DatabaseAPIRequests } from '../helpers/api/api-database' -import { DatabasesForImport } from '../types' - -export class MyRedisDatabasePage extends BaseOverviewPage { - // // Component Instances - // private readonly navigationPanel: NavigationPanel - - - readonly addRedisDatabaseDialog: AddRedisDatabaseDialog - - // private readonly insightsPanel: InsightsPanel - - // private readonly navigationHeader: NavigationHeader - - // private readonly authorizationDialog: AuthorizationDialog - - // API instance - private readonly databaseAPIRequests: DatabaseAPIRequests - - // CSS Selectors - private readonly cssNumberOfDbs: Locator - - private readonly cssRedisStackIcon: Locator - - // BUTTONS - private readonly deleteDatabaseButton: Locator - - private readonly confirmDeleteButton: Locator - - private readonly deleteButtonInPopover: Locator - - private readonly confirmDeleteAllDbButton: Locator - - private readonly editDatabaseButton: Locator - - private readonly popoverHeader: Locator - - private readonly submitChangesButton: Locator - - private readonly promoButton: Locator - - private readonly sortByDatabaseAlias: Locator - - private readonly sortByHostAndPort: Locator - - private readonly sortByConnectionType: Locator - - private readonly importDatabasesBtn: Locator - - private readonly retryImportBtn: Locator - - private readonly removeImportedFileBtn: Locator - - private readonly exportBtn: Locator - - private readonly exportSelectedDbsBtn: Locator - - private readonly userProfileBtn: Locator - - private readonly closeImportBtn: Locator - - // CHECKBOXES - private readonly selectAllCheckbox: Locator - - private readonly exportPasswordsCheckbox: Locator - - private readonly starFreeDbCheckbox: Locator - - // ICONS - private readonly moduleColumn: Locator - - private readonly moduleSearchIcon: Locator - - private readonly moduleGraphIcon: Locator - - private readonly moduleJSONIcon: Locator - - private readonly moduleTimeseriesIcon: Locator - - private readonly moduleBloomIcon: Locator - - private readonly moduleAIIcon: Locator - - private readonly moduleGearsIcon: Locator - - private readonly redisStackIcon: Locator - - private readonly tooltipRedisStackLogo: Locator - - private readonly iconNotUsedDatabase: Locator - - private readonly iconDeletedDatabase: Locator - - // TEXT INPUTS - private readonly searchInput: Locator - - private readonly importDatabaseInput: Locator - - // TEXT ELEMENTS - private readonly moduleTooltip: Locator - - private readonly moduleQuantifier: Locator - // - private readonly dbNameList: Locator - - private readonly tableRowContent: Locator - - private readonly hostPort: Locator - - private readonly failedImportMessage: Locator - - private readonly importResult: Locator - - private readonly userProfileAccountInfo: Locator - - private readonly portCloudDb: Locator - - // DIALOG - private readonly successResultsAccordion: Locator - - private readonly partialResultsAccordion: Locator - - private readonly failedResultsAccordion: Locator - - private readonly notificationUnusedDbMessage: Locator - - // CONTAINERS - private readonly databaseContainer: Locator - - private readonly connectionTypeTitle: Locator - - private readonly addDatabaseImport: Locator - - // Assumed additional selector needed in deleteDatabaseByName method - private readonly deleteRowButton: Locator - - constructor(page: Page, apiUr: string) { - super(page) - this.databaseAPIRequests = new DatabaseAPIRequests(apiUr) - - // Initialize component instances - // this.navigationPanel = new NavigationPanel(page) - this.addRedisDatabaseDialog = new AddRedisDatabaseDialog(page) - // this.insightsPanel = new InsightsPanel(page) - // this.navigationHeader = new NavigationHeader(page) - // this.authorizationDialog = new AuthorizationDialog(page) - - // CSS Selectors - this.cssNumberOfDbs = page.getByTestId('number-of-dbs') - this.cssRedisStackIcon = page.getByTestId('redis-stack-icon') - - // BUTTONS - this.deleteDatabaseButton = page.locator('[data-testid^="delete-instance-"]') - this.confirmDeleteButton = page.locator('[data-testid^="delete-instance-"]').filter({ hasText: 'Remove' }) - this.deleteButtonInPopover = page.locator('#deletePopover button') - this.confirmDeleteAllDbButton = page.getByTestId('delete-selected-dbs') - this.editDatabaseButton = page.locator('[data-testid^="edit-instance"]') - this.popoverHeader = page.locator('#formModalHeader') - this.submitChangesButton = page.getByTestId('btn-submit') - this.promoButton = page.getByTestId('promo-btn') - this.sortByDatabaseAlias = page.locator('span[title="Database Alias"]') - this.sortByHostAndPort = page.locator('span[title="Host:Port"]') - this.sortByConnectionType = page.locator('span[title="Connection Type"]') - this.importDatabasesBtn = page.getByTestId('option-btn-import') - this.retryImportBtn = page.getByTestId('btn-retry') - this.removeImportedFileBtn = page.locator('[aria-label="Clear selected files"]') - this.exportBtn = page.getByTestId('export-btn') - this.exportSelectedDbsBtn = page.getByTestId('export-selected-dbs') - this.userProfileBtn = page.getByTestId('user-profile-btn') - this.closeImportBtn = page.getByTestId('btn-close') - - // CHECKBOXES - this.selectAllCheckbox = page.locator('[data-test-subj="checkboxSelectAll"]') - this.exportPasswordsCheckbox = page.locator('[data-testid="export-passwords"] ~ div') - this.starFreeDbCheckbox = page.locator('[data-test-subj="checkboxSelectRow-create-free-cloud-db"]') - - // ICONS - this.moduleColumn = page.locator('[data-test-subj="tableHeaderCell_modules_3"]') - this.moduleSearchIcon = page.locator('[data-testid^="Redis Query Engine"]') - this.moduleGraphIcon = page.locator('[data-testid^="Graph"]') - this.moduleJSONIcon = page.locator('[data-testid^="JSON"]') - this.moduleTimeseriesIcon = page.locator('[data-testid^="Time Series"]') - this.moduleBloomIcon = page.locator('[data-testid^="Probabilistic"]') - this.moduleAIIcon = page.locator('[data-testid^="AI"]') - this.moduleGearsIcon = page.locator('[data-testid^="Gears"]') - this.redisStackIcon = page.getByTestId('redis-stack-icon') - this.tooltipRedisStackLogo = page.getByTestId('tooltip-redis-stack-icon') - this.iconNotUsedDatabase = page.locator('[data-testid^="database-status-tryDatabase-"]') - this.iconDeletedDatabase = page.locator('[data-testid^="database-status-checkIfDeleted-"]') - - // TEXT INPUTS - this.searchInput = page.getByTestId('search-database-list') - this.importDatabaseInput = page.getByTestId('import-file-modal-filepicker') - - // TEXT ELEMENTS - this.moduleTooltip = page.locator('.euiToolTipPopover') - this.moduleQuantifier = page.getByTestId('_module') - this.dbNameList = page.locator('[data-testid^="instance-name"]') - this.tableRowContent = page.locator('[data-test-subj="database-alias-column"]') - this.hostPort = page.getByTestId('host-port') - this.failedImportMessage = page.getByTestId('result-failed') - this.importResult = page.locator('[data-testid^="table-result-"]') - this.userProfileAccountInfo = page.locator('[data-testid^="profile-account-"]') - this.portCloudDb = page.locator('[class*="column_host"]') - - // DIALOG - this.successResultsAccordion = page.locator('[data-testid^="success-results-"]') - this.partialResultsAccordion = page.locator('[data-testid^="partial-results-"]') - this.failedResultsAccordion = page.locator('[data-testid^="failed-results-"]') - this.notificationUnusedDbMessage = page.locator('[class^="_warningTooltipContent"]') - - // CONTAINERS - this.databaseContainer = page.locator('.databaseContainer') - this.connectionTypeTitle = page.locator('[data-test-subj="tableHeaderCell_connectionType_2"]') - this.addDatabaseImport = page.getByTestId('add-db_import') - - // Additional property assumed for deleteDatabaseByName: - this.deleteRowButton = page.locator('[data-testid^="delete-instance-"]') - } - - async clickOnDBByName(dbName: string): Promise { - const toast = new Toast(this.page) - if (await toast.isCloseButtonVisible()) { - await toast.closeToast() - } - const db = this.dbNameList.filter({ hasText: dbName.trim() }) - await expect(db).toBeVisible({ timeout: 10000 }) - await db.first().click() - } - - // async deleteAllDatabases(): Promise { - // await this.navigationPanel.myRedisDBButton.click() - // const dbNames = this.tableRowContent - // const count = await dbNames.count() - // if (count > 1) { - // await this.selectAllCheckbox.click() - // await this.deleteButtonInPopover.click() - // await this.confirmDeleteAllDbButton.click() - // } else if (count === 1) { - // await this.deleteDatabaseButton.click() - // await this.confirmDeleteButton.click() - // } - // if (await this.toast.toastCloseButton.isVisible()) { - // await this.toast.toastCloseButton.click() - // } - // } - - async deleteDatabaseByName(dbName: string): Promise { - const dbNames = this.tableRowContent - const count = await dbNames.count() - for (let i = 0; i < count; i++) { - const text = (await dbNames.nth(i).textContent()) || '' - if (text.includes(dbName)) { - // Assuming deleteRowButton corresponds to a delete button for the row, - // and we click the one at index i-1 (as per original logic) - await this.deleteRowButton.nth(i - 1).click() - await this.confirmDeleteButton.click() - break - } - } - } - - async clickOnEditDBByName(databaseName: string): Promise { - const dbNames = this.dbNameList - const count = await dbNames.count() - for (let i = 0; i < count; i++) { - const text = (await dbNames.nth(i).textContent()) || '' - if (text.includes(databaseName)) { - await this.editDatabaseButton.nth(i).click() - break - } - } - } - - async checkModulesInTooltip(moduleNameList: string[]): Promise { - for (const item of moduleNameList) { - await expect(this.moduleTooltip.locator('span', { hasText: `${item} v.` })).toBeVisible() - } - } - - async checkModulesOnPage(moduleList: Locator[]): Promise { - for (const item of moduleList) { - await expect(item).toBeVisible() - } - } - - async getAllDatabases(): Promise { - const databases: string[] = [] - await expect(this.dbNameList).toBeVisible() - const n = await this.dbNameList.count() - for (let k = 0; k < n; k++) { - const name = await this.dbNameList.nth(k).textContent() - databases.push(name || '') - } - return databases - } - - async compareDatabases(actualList: string[], sortedList: string[]): Promise { - for (let k = 0; k < actualList.length; k++) { - await expect(actualList[k].trim()).toEqual(sortedList[k].trim()) - } - } - - async verifyDatabaseStatusIsVisible(databaseName: string): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) - const databaseNewPoint = this.page.getByTestId(`database-status-new-${databaseId}`) - await expect(databaseNewPoint).toBeVisible() - } - - async verifyDatabaseStatusIsNotVisible(databaseName: string): Promise { - const databaseId = await this.databaseAPIRequests.getDatabaseIdByName(databaseName) - const databaseEditBtn = this.page.getByTestId(`database-status-new-${databaseId}`) - await expect(databaseEditBtn).not.toBeVisible() - } - - getDatabaseNamesFromListByResult(listOfDb: DatabasesForImport, result: string): string[] { - return listOfDb.filter(element => element.result === result).map(item => item.name!) - } -} diff --git a/tests/playwright/pageObjects/rdi-instances-list-page.ts b/tests/playwright/pageObjects/rdi-instances-list-page.ts index ee501ee81b..42e11d4e94 100755 --- a/tests/playwright/pageObjects/rdi-instances-list-page.ts +++ b/tests/playwright/pageObjects/rdi-instances-list-page.ts @@ -1,11 +1,11 @@ +/* eslint-disable no-await-in-loop */ import { Page, Locator, expect } from '@playwright/test' import { BaseOverviewPage } from './base-overview-page' -import { AddRdiInstanceDialog, RdiInstance } from './dialogs/add-rdi-instance-dialog' +import { AddRdiInstanceDialog } from './dialogs/add-rdi-instance-dialog' +import { RdiInstance } from '../types/rdi' export class RdiInstancesListPage extends BaseOverviewPage { - readonly page: Page - - // readonly AddRdiInstanceDialog: AddRdiInstanceDialog + readonly AddRdiInstanceDialog: AddRdiInstanceDialog readonly addRdiInstanceButton: Locator @@ -44,7 +44,7 @@ export class RdiInstancesListPage extends BaseOverviewPage { super(page) this.page = page - // this.AddRdiInstanceDialog = new AddRdiInstanceDialog(page) + this.AddRdiInstanceDialog = new AddRdiInstanceDialog(page) // Use getByTestId for selectors with data-testid this.addRdiInstanceButton = page.getByTestId('rdi-instance') @@ -75,18 +75,24 @@ export class RdiInstancesListPage extends BaseOverviewPage { } } - // /** - // * Add Rdi instance. - // * @param instanceValue Rdi instance data - // */ - // async addRdi(instanceValue: RdiInstance): Promise { - // await this.page.click(this.addRdiInstanceButton) - // await this.page.fill(this.AddRdiInstanceDialog.rdiAliasInput, instanceValue.alias) - // await this.page.fill(this.AddRdiInstanceDialog.urlInput, instanceValue.url) - // await this.page.fill(this.AddRdiInstanceDialog.usernameInput, instanceValue.username) - // await this.page.fill(this.AddRdiInstanceDialog.passwordInput, instanceValue.password) - // await this.page.click(this.AddRdiInstanceDialog.addInstanceButton) - // } + /** + * Add Rdi instance. + * @param instanceValue Rdi instance data + */ + async addRdi(instanceValue: RdiInstance): Promise { + await this.addRdiInstanceButton.click() + await this.AddRdiInstanceDialog.rdiAliasInput.fill(instanceValue.alias) + await this.AddRdiInstanceDialog.urlInput.fill(instanceValue.url) + if (instanceValue.username) { + await this.AddRdiInstanceDialog.usernameInput.fill(instanceValue.username) + } + if (instanceValue.password) { + await this.AddRdiInstanceDialog.passwordInput.fill(instanceValue.password) + } + await this.AddRdiInstanceDialog.addInstanceButton.click() + // Wait for the dialog to close after adding the Rdi instance + await this.AddRdiInstanceDialog.connectToRdiForm.waitFor({ state: 'hidden' }) + } /** * Get Rdi instance values by index. @@ -115,11 +121,11 @@ export class RdiInstancesListPage extends BaseOverviewPage { async deleteRdiByName(dbName: string): Promise { const dbNames = this.rdiInstanceRow const count = await dbNames.count() - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i += 1) { const text = await dbNames.nth(i).innerText() if (text.includes(dbName)) { - await this.page.click(this.deleteRowButton.nth(i)) - await this.page.click(this.confirmDeleteButton) + await this.deleteRowButton.nth(i).click() + await this.confirmDeleteButton.click() break } } @@ -132,10 +138,10 @@ export class RdiInstancesListPage extends BaseOverviewPage { async clickEditRdiByName(dbName: string): Promise { const rdiNames = this.rdiInstanceRow const count = await rdiNames.count() - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i += 1) { const text = await rdiNames.nth(i).innerText() if (text.includes(dbName)) { - await this.page.click(this.editRowButton.nth(i)) + await this.editRowButton.nth(i).click() break } } @@ -147,7 +153,7 @@ export class RdiInstancesListPage extends BaseOverviewPage { */ async clickRdiByName(rdiName: string): Promise { if (await this.Toast.toastCloseButton.isVisible()) { - await this.page.click(this.Toast.toastCloseButton) + await this.Toast.toastCloseButton.click() } // Use getByText with exact match for the Rdi name const rdi = this.rdiNameList.getByText(rdiName.trim(), { exact: true }) @@ -160,7 +166,7 @@ export class RdiInstancesListPage extends BaseOverviewPage { * @param columnName The name of the column. */ async sortByColumn(columnName: string): Promise { - await this.page.click(this.sortBy.filter({ hasText: columnName })) + await this.sortBy.filter({ hasText: columnName }).click() } /** @@ -169,7 +175,7 @@ export class RdiInstancesListPage extends BaseOverviewPage { async getAllRdiNames(): Promise { const rdis: string[] = [] const count = await this.rdiInstanceRow.count() - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i += 1) { const name = await this.rdiInstanceRow.nth(i).locator(this.cssRdiAlias).innerText() rdis.push(name) } diff --git a/tests/playwright/selectors/toast-selectors.js b/tests/playwright/selectors/toast-selectors.js deleted file mode 100644 index 1b0e794cd3..0000000000 --- a/tests/playwright/selectors/toast-selectors.js +++ /dev/null @@ -1,9 +0,0 @@ -export const ToastSelectors = { - toastHeader: '[data-test-subj=euiToastHeader]', - toastBody: '[class*=euiToastBody]', - toastSuccess: '[class*=euiToast--success]', - toastError: '[class*=euiToast--danger]', - toastCloseButton: '[data-test-subj=toastCloseButton]', - toastSubmitBtn: 'submit-tooltip-btn', - toastCancelBtn: 'toast-cancel-btn' -}; diff --git a/tests/playwright/selectors/toast-selectors.ts b/tests/playwright/selectors/toast-selectors.ts new file mode 100644 index 0000000000..8d60edd874 --- /dev/null +++ b/tests/playwright/selectors/toast-selectors.ts @@ -0,0 +1,9 @@ +export const ToastSelectors = { + toastHeader: '[data-test-subj=euiToastHeader]', + toastBody: '[class*=euiToastBody]', + toastSuccess: '[class*=euiToast--success]', + toastError: '[class*=euiToast--danger]', + toastCloseButton: '[data-test-subj=toastCloseButton]', + toastSubmitBtn: 'submit-tooltip-btn', + toastCancelBtn: 'toast-cancel-btn', +} diff --git a/tests/playwright/selectors/user-agreement-selectors.ts b/tests/playwright/selectors/user-agreement-selectors.ts index f3d7b61810..f0f0105c8c 100644 --- a/tests/playwright/selectors/user-agreement-selectors.ts +++ b/tests/playwright/selectors/user-agreement-selectors.ts @@ -4,5 +4,5 @@ export const UserAgreementSelectors = { switchOptionEula: 'switch-option-eula', switchOptionEncryption: 'switch-option-encryption', pluginSectionWithText: 'plugin-section', - recommendedSwitcher: 'switch-option-recommended' -}; + recommendedSwitcher: 'switch-option-recommended', +} diff --git a/tests/playwright/tests/example2.spec.ts b/tests/playwright/tests/basic-navigation.spec.ts similarity index 100% rename from tests/playwright/tests/example2.spec.ts rename to tests/playwright/tests/basic-navigation.spec.ts diff --git a/tests/playwright/tests/example.spec.ts b/tests/playwright/tests/example.spec.ts deleted file mode 100644 index fe7609a7ac..0000000000 --- a/tests/playwright/tests/example.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable no-empty-pattern */ -import { Common } from '../helpers/common' -import { BrowserPage } from '../pageObjects/browser-page' -import { DatabaseHelper } from '../helpers/database' -import { APIKeyRequests } from '../helpers/api/api-keys' -import { DatabaseAPIRequests } from '../helpers/api/api-databases' -import { test, expect } from '../fixtures/test' -import { apiUrl, ossStandaloneConfig } from '../helpers/conf' - -let keyName: string -let browserPage: BrowserPage -let databaseHelper: DatabaseHelper - -test.beforeEach(async ({ page }) => { - // await electronPage.getByText('Add Redis').click() - browserPage = new BrowserPage(page) - databaseHelper = new DatabaseHelper(page, apiUrl) - await databaseHelper.acceptLicenseTermsAndAddDatabaseApi( - ossStandaloneConfig, - page, - apiUrl, - ) - keyName = Common.generateAlphanumeric(5) -}) - -test.afterEach(async ({}) => { - const apiKeyClient = new APIKeyRequests(apiUrl) - const dbApi = new DatabaseAPIRequests(apiUrl) - - try { - await apiKeyClient.deleteKeyByNameApi( - keyName, - ossStandaloneConfig.databaseName, - await browserPage.getWindowId(), - ) - await dbApi.deleteStandaloneDatabaseApi( - ossStandaloneConfig, - await browserPage.getWindowId(), - ) - } catch (error) { - console.warn('Error during cleanup:', error) - } -}) - -test('basic test', async () => { - await browserPage.addHashKey(keyName) - - // checks that the notification is displayed (should be in a different test) - await expect( - await browserPage.page.getByText('Key has been added'), - ).toBeVisible() - - // Check that new key is displayed in the list - await browserPage.searchByKeyName(keyName) - const isKeyIsDisplayedInTheList = - await browserPage.isKeyIsDisplayedInTheList(keyName) - await expect(isKeyIsDisplayedInTheList).toBe(true) -}) diff --git a/tests/playwright/tests/keys.spec.ts b/tests/playwright/tests/keys.spec.ts new file mode 100644 index 0000000000..b695b5bb73 --- /dev/null +++ b/tests/playwright/tests/keys.spec.ts @@ -0,0 +1,101 @@ +/* eslint-disable no-empty-pattern */ +import { faker } from '@faker-js/faker' + +import { BrowserPage } from '../pageObjects/browser-page' +import { test, expect } from '../fixtures/test' +import { ossStandaloneConfig } from '../helpers/conf' +import { + addStandaloneInstanceAndNavigateToIt, + navigateToStandaloneInstance, +} from '../helpers/utils' + +test.describe('Adding Database Keys', () => { + let browserPage: BrowserPage + let keyName: string + let cleanupInstance: () => Promise + + test.beforeEach(async ({ page, api: { databaseService } }) => { + browserPage = new BrowserPage(page) + keyName = faker.string.alphanumeric(10) + cleanupInstance = await addStandaloneInstanceAndNavigateToIt( + page, + databaseService, + ) + + await navigateToStandaloneInstance(page) + }) + + test.afterEach(async ({ api: { keyService } }) => { + // Clean up: delete the key + await keyService.deleteKeyByNameApi( + keyName, + ossStandaloneConfig.databaseName, + ) + + await cleanupInstance() + }) + + test('Verify that user can add Hash Key', async ({}) => { + await browserPage.addHashKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add Set Key', async ({}) => { + await browserPage.addSetKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add List Key', async ({}) => { + await browserPage.addListKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add String Key', async ({}) => { + await browserPage.addStringKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add ZSet Key', async ({}) => { + const scores = '111' + await browserPage.addZSetKey(keyName, scores) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add Stream key', async ({}) => { + const keyField = faker.string.alphanumeric(20) + const keyValue = faker.string.alphanumeric(20) + + await browserPage.addStreamKey(keyName, keyField, keyValue) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) +}) diff --git a/tests/playwright/types/connections.ts b/tests/playwright/types/connections.ts index 2da226ee93..ef9cd87025 100644 --- a/tests/playwright/types/connections.ts +++ b/tests/playwright/types/connections.ts @@ -11,12 +11,6 @@ export type SentinelParameters = { name?: string[] } -export type OSSClusterParameters = { - ossClusterHost: string - ossClusterPort: string - ossClusterDatabaseName: string -} - export type SSHParameters = { sshHost: string sshPort: string @@ -25,13 +19,3 @@ export type SSHParameters = { sshPrivateKey?: string sshPassphrase?: string } - -/** - * Nodes in OSS Cluster parameters - * @param host The host of the node - * @param port The port of the node - */ -export type ClusterNodes = { - host: string - port: string -} diff --git a/tests/playwright/types/databes.ts b/tests/playwright/types/databes.ts index 8408771e1a..9facc5810c 100644 --- a/tests/playwright/types/databes.ts +++ b/tests/playwright/types/databes.ts @@ -38,3 +38,47 @@ export type AddNewDatabaseParameters = { key?: string } } + +export type DatabaseInstance = { + host: string + port: number + provider?: string + id: string + connectionType?: string + lastConnection?: Date + password?: string + username?: string + name?: string + db?: number + tls?: boolean + ssh?: boolean + sshOptions?: { + host: string + port: number + username?: string + password?: string | true + privateKey?: string + passphrase?: string | true + } + tlsClientAuthRequired?: boolean + verifyServerCert?: boolean + caCert?: object + clientCert?: object + authUsername?: string + authPass?: string + isDeleting?: boolean + sentinelMaster?: object + modules: object[] + version: string + isRediStack?: boolean + visible?: boolean + loading?: boolean + isFreeDb?: boolean + tags?: { + id: string + key: string + value: string + createdAt: string + updatedAt: string + }[] +} diff --git a/tests/playwright/types/index.ts b/tests/playwright/types/index.ts index c49b877a2b..86cc0bff92 100644 --- a/tests/playwright/types/index.ts +++ b/tests/playwright/types/index.ts @@ -1,3 +1,9 @@ export * from './databes' export * from './connections' export * from './keys' + +declare global { + interface Window { + windowId?: string + } +} diff --git a/tests/playwright/types/rdi.ts b/tests/playwright/types/rdi.ts new file mode 100644 index 0000000000..0c88a211f9 --- /dev/null +++ b/tests/playwright/types/rdi.ts @@ -0,0 +1,8 @@ +export type RdiInstance = { + alias: string + url: string + version?: string + lastConnection?: string + username?: string + password?: string +} From 72e113c0bf0e4c74f6c96d8819fa8287ca14d272 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Sun, 8 Jun 2025 22:10:31 +0300 Subject: [PATCH 118/128] use exact match for button --- tests/playwright/pageObjects/browser-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/pageObjects/browser-page.ts b/tests/playwright/pageObjects/browser-page.ts index ef6b4f3703..7e83a086bb 100755 --- a/tests/playwright/pageObjects/browser-page.ts +++ b/tests/playwright/pageObjects/browser-page.ts @@ -308,7 +308,7 @@ export class BrowserPage extends BasePage { this.saveMemberButton = page.getByTestId('save-members-btn') this.searchButtonInKeyDetails = page.getByTestId('search-button') this.addKeyButton = page.locator('button', { - hasText: 'Add Key', + hasText: /^Add Key$/, }) this.keyTypeDropDown = page.locator( 'fieldset button.euiSuperSelectControl', From dda1c486af9a8061b91709eaf20c4639d74dd66a Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Mon, 9 Jun 2025 10:03:00 +0300 Subject: [PATCH 119/128] update README.md --- tests/playwright/README.md | 199 +++++++++++-------------------------- 1 file changed, 56 insertions(+), 143 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 1f0f1c3c58..9ba3abc20c 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,24 +1,57 @@ +## Description -## Install +Playwright tests project for Redisinsight + +## Preconditions for running locally + +Note: this relies on docker RTE and RI images and configs from the tests/e2e project + +1. spin up the Redis test environment container from tests/e2e/ project: +```shell + docker compose -p test-docker -f ../e2e/rte.docker-compose.yml up --force-recreate —detach +``` +2. for the Docker RI build: + +- build the image locally or trigger a [GitHub action](https://github.com/RedisInsight/RedisInsight/actions/workflows/manual-build.yml) to do so and download the artifact (docker-linux-alpine.amd64.tar) +- load the image +```shell + docker load -i docker-linux-alpine.amd64.tar +``` +- compose a container `docker compose -p e2e-ri-docker -f ../e2e/docker.web.docker-compose.yml up --detach --force-recreate` + - Note: you need RI_ENCRYPTION_KEY, RI_SERVER_TLS_CERT, RI_SERVER_TLS_KEY environment variables for this step (in tests/e2e/.env) +- once running you should be able to access the app on https://localhost:5540 +3. for Electron RI build: + +- build from root project `shell package:prod`. This creates a /release folder. +- change `ELECTRON_EXECUTABLE_PATH` in tests/playwright/env/.desktop.env to point to the executable file (MacOS by default) + +4. for local web build: + +- make sure the Docker RI build container from step 2. is stopped as the api shares the same port (5540) +- start `shell dev:ui` and `shell dev:api` from the root project +- once running you should be able to access the app on http://localhost:8080 + +## Install ```shell yarn install ``` -Install Playwright browsers +Install Playwright browsers + ```shell yarn playwright install ``` -Install Playwright operating system dependencies requires sudo / root +Install Playwright operating system dependencies requires sudo / root + ```shell sudo yarn playwright install-deps ``` [More info on running tests](https://playwright.dev/docs/running-tests) - ## Extra tooling Auto-generate tests with Codegen. @@ -33,146 +66,44 @@ Starts the interactive UI mode. This also can be set in the config. yarn playwright test --ui ``` +## Running tests -Allure report display needs JAVA_HOME set -and to run the server, JDK version 8 to 11 is required. Otherwise, you get: -``` -Starting web server... -Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: /usr/lib/jvm/java-17-openjdk-amd64/lib/libawt_xawt.so -at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2398) -at java.base/java.lang.Runtime.load0(Runtime.java:755) -at java.base/java.lang.System.load(System.java:1970) -at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) -``` - -However, running the report from a server or IDE also works, so it is up to you. - -## Writing test -Currently, the flow once the run starts is: provide some configuration based on `playwright.config.ts` [test project](https://playwright.dev/docs/test-projects) -(project can be viewed as a way to parametrize the tests) to the [fixture](https://playwright.dev/docs/next/test-fixtures#introduction) of the test located in the [fixtures folder](./fixtures). -In the folder, there is a base fixture to start the browser that is required. Depending on the required steps, you can do actions even before and after hooks in the tests. -A good example is running services, browsers, etc. From the fixture, you can pass down test configuration or any other data required. -Once in the test, the run follows any regular unit testing framework flow. - -```mermaid -sequenceDiagram - participant Config - participant Fixture - participant Test - participant Report - Config ->>Fixture: Pass the values from playwrite.config.ts
to fixture - Fixture ->>Fixture: Self Config using dotEnv - Fixture ->>Test: Setup and trigger everything necessary
for the test and maybe pass init/config if needed - Test ->>Report: Report the result, attach logs, artefacts - -``` -Some more useful links for Playwright: -https://playwright.dev/docs/locators#quick-guide -https://playwright.dev/docs/api/class-electronapplication#electron-application-evaluate - -## Running test -### Preconditions - -For RedisApp for Electron, AppImage (path in `playwright.config.js`) is required or change the path to the appropriate build folder on your machine. -For Docker testing, you need to load the Docker image for your system (or run the app from your code base): +Runs the end-to-end tests for the Docker RI build in Chromium browser (also runs in CI): ```shell - docker load -i docker-linux-alpine.amd64.tar - or - docker load -i docker-linux-alpine.arm64.tar + yarn test:chromium:docker ``` -and change the config in playwright.config,js. -For loading Redis databases and data similar to the other project, you have the [local-docker-environment](./local-docker-environment) folder. -Fill the `.env` file similar to [the setup in Confluence](https://redislabs.atlassian.net/wiki/spaces/DX/pages/4906319969/Mac+setup+e2e+tests) -and execute: +Runs the end-to-end tests for the Docker RI build in Chromium browser in debug mode: ```shell - ./local-docker-environment/create_local_environment.sh -``` - -To destroy the environment use: -```shell - ./local-docker-environment/destroy_local_environment.sh -``` - -## Runs - -Runs the end-to-end tests (all projects): - -``` shell - yarn playwright test + yarn test:chromium:docker:debug ``` -Runs the tests only on Desktop Chrome. +Runs the end-to-end tests for the Docker RI build in Chromium browser for a specific .spec file: ```shell - yarn test:chromium:docker + yarn yarn test:chromium:docker basic-navigation ``` -To run Electron tests: +Runs the end-to-end tests for the Electron RI build. ```shell yarn test:electron ``` - -Runs the tests in framework debug mode. - -``` -yarn playwright test --project=DockerBuild --debug -``` - -Runs the tests in a specific file: +Runs the end-to-end tests for the local web environment (client:8080, api:5540). ```shell - yarn playwright test example + yarn test:chromium:local-web ``` -### Electron testing - -Tests and setup are quite similar to browser tests. In the Electron case, `baseURL` is the path on the local file system for a prebuilt/installed app -or an AppImage. In my case, I used an AppImage for Linux, so the config was: - -```json lines -use: { - baseURL: '/home/tsvetan-tsvetkov/Downloads/Redis-Insight-linux-x86_64.AppImage', - apiUrl: 'https://localhost:5530/api', - headless: false, - - }, -``` - -That section for macOS, where I used an installed app, looked like: - -```json lines -use: { - baseURL: '/Users/tsvetantsvetkov/Applications/RedisInsight.app/Contents/MacOS/Redis Insight', - apiUrl: 'https://localhost:5530/api', - headless: false, - - }, -``` -Execution-wise, there were no changes. An interesting project that I came across while investigating starting Electron issues -is based on this blog post: [Testing Electron Apps with Playwright](https://dev.to/kubeshop/testing-electron-apps-with-playwright-3f89). -The code base [is here](https://github.com/kubeshop/monokle/blob/main/tests/electronHelpers.ts). - -They are using this helper for when a new build is created, starting the test automatically by finding the latest build, -setting the correct paths, and running the tests. So it might be useful to have something similar implemented. - -## Debug - -Add `DEBUG=pw:api` as an environment variable. This will enable the framework debug mode plus will enable log interceptors for the Axios client. - ## Reports -Running: - -```shell - yarn test:chromium -``` +Allure report display needs JAVA_HOME set +and to run the server, JDK version 8 to 11 is required. -will generate HTML and Allure reports. However, those reports are for a single run. In order to have history, which is more useful, run: +Running e2e tests will generate HTML and Allure reports. However, those reports are for a single run. In order to have history, which is more useful, run: ```shell yarn test:allureHistoryReport @@ -183,27 +114,9 @@ added to this report. For more information, see: https://allurereport.org/docs/p Some rough execution time comparison for the same test: -| Test Name | Framework | Browser | Duration| -|------------------------|------------|----------|---------| -|Verify that user can add Hash Key| TestCafe | Chromium |27s| -|Verify that user can add Hash Key| PlayWright | Chromium |10s| -|Verify that user can add Hash Key| TestCafe | Eelctron |30s| -|Verify that user can add Hash Key| PlayWright | Eelctron |18s| - - -## Improvements - -Since this was a proof of concept, a small portion of the code was moved to make it work. -However, the test code is quite messy, the configuration is scattered, and the naming conventions are confusing. -It needs to be: -- decoupled -- simplified - -This applies to all Page Objects, Actions, and Helpers. For all tests: -- test scope should be reduced -- test should start in a known state -- test state should be cleared -- test should be able to run in parallel - -(Part of what can help partly with the state management bring up specific environment in the fixture and tear it down after -the test is done, group tests that use different states. this will increase execution time but might be with it in terms of stability) +| Test Name | Framework | Browser | Duration | +| --------------------------------- | ---------- | -------- | -------- | +| Verify that user can add Hash Key | TestCafe | Chromium | 27s | +| Verify that user can add Hash Key | PlayWright | Chromium | 10s | +| Verify that user can add Hash Key | TestCafe | Eelctron | 30s | +| Verify that user can add Hash Key | PlayWright | Eelctron | 18s | From 45a0fc69618a0f2e8d98c6fbfcde123783411bc2 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Mon, 9 Jun 2025 10:19:50 +0300 Subject: [PATCH 120/128] update README.md --- tests/playwright/README.md | 196 +++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 94 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 9ba3abc20c..a56089c961 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,122 +1,130 @@ -## Description - -Playwright tests project for Redisinsight - -## Preconditions for running locally - -Note: this relies on docker RTE and RI images and configs from the tests/e2e project - -1. spin up the Redis test environment container from tests/e2e/ project: -```shell - docker compose -p test-docker -f ../e2e/rte.docker-compose.yml up --force-recreate —detach -``` -2. for the Docker RI build: - -- build the image locally or trigger a [GitHub action](https://github.com/RedisInsight/RedisInsight/actions/workflows/manual-build.yml) to do so and download the artifact (docker-linux-alpine.amd64.tar) -- load the image -```shell - docker load -i docker-linux-alpine.amd64.tar -``` -- compose a container `docker compose -p e2e-ri-docker -f ../e2e/docker.web.docker-compose.yml up --detach --force-recreate` - - Note: you need RI_ENCRYPTION_KEY, RI_SERVER_TLS_CERT, RI_SERVER_TLS_KEY environment variables for this step (in tests/e2e/.env) -- once running you should be able to access the app on https://localhost:5540 - -3. for Electron RI build: - -- build from root project `shell package:prod`. This creates a /release folder. -- change `ELECTRON_EXECUTABLE_PATH` in tests/playwright/env/.desktop.env to point to the executable file (MacOS by default) - -4. for local web build: - -- make sure the Docker RI build container from step 2. is stopped as the api shares the same port (5540) -- start `shell dev:ui` and `shell dev:api` from the root project -- once running you should be able to access the app on http://localhost:8080 - -## Install - +# RedisInsight Playwright Tests + +This project contains Playwright tests for RedisInsight. + +## Prerequisites + +### General Requirements +- Docker installed and running. +- Redis test environment and RedisInsight configurations from the `tests/e2e` project. + +### Steps to Set Up + +1. **Start the Redis Test Environment** + Navigate to the `tests/e2e` directory and run: + ```shell + docker compose -p test-docker -f rte.docker-compose.yml up --force-recreate --detach + ``` + +2. **For Docker RI Build** + - Build the Docker image locally or trigger a [GitHub Action](https://github.com/RedisInsight/RedisInsight/actions/workflows/manual-build.yml) to build and download the artifact (`docker-linux-alpine.amd64.tar`). + - Load the image: + ```shell + docker load -i docker-linux-alpine.amd64.tar + ``` + - Navigate to the `tests/e2e` directory and start the container: + ```shell + docker compose -p e2e-ri-docker -f docker.web.docker-compose.yml up --detach --force-recreate + ``` + - Ensure the following environment variables are set in `tests/e2e/.env`: + - `RI_ENCRYPTION_KEY` + - `RI_SERVER_TLS_CERT` + - `RI_SERVER_TLS_KEY` + - Access the app at: `https://localhost:5540`. + +3. **For Electron RI Build** + - Build the project from the root directory: + ```shell + yarn package:prod + ``` + - Update `ELECTRON_EXECUTABLE_PATH` in `tests/playwright/env/.desktop.env` to point to the generated executable file (MacOS by default). + +4. **For Local Web Build** + - Stop the Docker RI container from step 2 (to free up port 5540). + - Start the UI and API servers: + ```shell + yarn dev:ui + yarn dev:api + ``` + - Access the app at: `http://localhost:8080`. + +## Installation + +1. Install dependencies: + ```shell + yarn install + ``` + +2. Install Playwright browsers: + ```shell + yarn playwright install + ``` + +3. Install Playwright OS dependencies (requires `sudo`): + ```shell + sudo yarn playwright install-deps + ``` + +For more details, refer to the [Playwright documentation](https://playwright.dev/docs/running-tests). + +## Running Tests + +### Docker RI Build +Run end-to-end tests in Chromium: ```shell - yarn install +yarn test:chromium:docker ``` -Install Playwright browsers - +Run tests in debug mode: ```shell - yarn playwright install +yarn test:chromium:docker:debug ``` -Install Playwright operating system dependencies requires sudo / root - +Run tests for a specific `.spec` file: ```shell - sudo yarn playwright install-deps +yarn test:chromium:docker basic-navigation ``` -[More info on running tests](https://playwright.dev/docs/running-tests) - -## Extra tooling - -Auto-generate tests with Codegen. - +### Electron RI Build +Run end-to-end tests for the Electron build: ```shell - yarn playwright codegen +yarn test:electron ``` -Starts the interactive UI mode. This also can be set in the config. - +### Local Web Environment +Run tests for the local web environment: ```shell - yarn playwright test --ui +yarn test:chromium:local-web ``` -## Running tests - -Runs the end-to-end tests for the Docker RI build in Chromium browser (also runs in CI): +## Extra Tooling +### Auto-Generate Tests +Use Playwright's Codegen to auto-generate tests: ```shell - yarn test:chromium:docker +yarn playwright codegen ``` -Runs the end-to-end tests for the Docker RI build in Chromium browser in debug mode: - -```shell - yarn test:chromium:docker:debug -``` - -Runs the end-to-end tests for the Docker RI build in Chromium browser for a specific .spec file: - +### Interactive UI Mode +Start Playwright's interactive UI mode: ```shell - yarn yarn test:chromium:docker basic-navigation -``` - -Runs the end-to-end tests for the Electron RI build. - -```shell - yarn test:electron -``` - -Runs the end-to-end tests for the local web environment (client:8080, api:5540). - -```shell - yarn test:chromium:local-web +yarn playwright test --ui ``` ## Reports -Allure report display needs JAVA_HOME set -and to run the server, JDK version 8 to 11 is required. - -Running e2e tests will generate HTML and Allure reports. However, those reports are for a single run. In order to have history, which is more useful, run: - -```shell -yarn test:allureHistoryReport -``` - -Or change it depending on your needs to enable history for the report that can show you around 20 executions. Additional info can be -added to this report. For more information, see: https://allurereport.org/docs/playwright-reference/ - -Some rough execution time comparison for the same test: +### Allure Reports +- Ensure `JAVA_HOME` is set and JDK version 8 to 11 is installed. +- Generate a report with history: + ```shell + yarn test:allureHistoryReport + ``` +- For more details, refer to the [Allure documentation](https://allurereport.org/docs/playwright-reference/). +### Execution Time Comparison | Test Name | Framework | Browser | Duration | | --------------------------------- | ---------- | -------- | -------- | | Verify that user can add Hash Key | TestCafe | Chromium | 27s | -| Verify that user can add Hash Key | PlayWright | Chromium | 10s | -| Verify that user can add Hash Key | TestCafe | Eelctron | 30s | -| Verify that user can add Hash Key | PlayWright | Eelctron | 18s | +| Verify that user can add Hash Key | Playwright | Chromium | 10s | +| Verify that user can add Hash Key | TestCafe | Electron | 30s | +| Verify that user can add Hash Key | Playwright | Electron | 18s | From 1ffaa09ac5f5fd98c4280af5651dc7480579544f Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Mon, 9 Jun 2025 10:32:23 +0300 Subject: [PATCH 121/128] update README.md --- tests/playwright/README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index a56089c961..5c629962a7 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,6 +1,14 @@ # RedisInsight Playwright Tests -This project contains Playwright tests for RedisInsight. +This project contains Playwright tests for RedisInsight. +It supports running tests on Electron, Docker and Web Redisinsight builds. + +## Folder structure + +- `/env` - contains env configs for the 3 types of builds. +- `/tests` - Contains the actual tests. +- `/helpers/api` - ported some api helpers from the tests/e2e project. They are used for setting up data. +- `/pageObjects` - ported page element locators and logic from the tests/e2e project. ## Prerequisites @@ -22,14 +30,14 @@ This project contains Playwright tests for RedisInsight. ```shell docker load -i docker-linux-alpine.amd64.tar ``` - - Navigate to the `tests/e2e` directory and start the container: - ```shell - docker compose -p e2e-ri-docker -f docker.web.docker-compose.yml up --detach --force-recreate - ``` - Ensure the following environment variables are set in `tests/e2e/.env`: - `RI_ENCRYPTION_KEY` - `RI_SERVER_TLS_CERT` - `RI_SERVER_TLS_KEY` + - Navigate to the `tests/e2e` directory and start the container: + ```shell + docker compose -p e2e-ri-docker -f docker.web.docker-compose.yml up --detach --force-recreate + ``` - Access the app at: `https://localhost:5540`. 3. **For Electron RI Build** From 2a60c4405ee3b1589947df017a4db5a5910db7ee Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Mon, 9 Jun 2025 10:35:00 +0300 Subject: [PATCH 122/128] revert tests/e2e change --- tests/e2e/helpers/insights.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/e2e/helpers/insights.ts b/tests/e2e/helpers/insights.ts index bb10a6930d..24ced1b965 100644 --- a/tests/e2e/helpers/insights.ts +++ b/tests/e2e/helpers/insights.ts @@ -38,9 +38,7 @@ export async function modifyFeaturesConfigJson(filePath: string): Promise */ export async function updateControlNumber(controlNumber: number): Promise { await syncFeaturesApi(); - if(process.env.RI_SOCKETS_CORS){ - await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }); - } + await DatabaseScripts.updateColumnValueInDBTable({ ...dbTableParams, rowValue: controlNumber }); await syncFeaturesApi(); await t.eval(() => location.reload()); } @@ -52,8 +50,6 @@ export async function refreshFeaturesTestData(): Promise { const defaultConfigPath = path.join('.', 'test-data', 'features-configs', 'insights-default-remote.json'); await modifyFeaturesConfigJson(defaultConfigPath); - if(process.env.RI_SOCKETS_CORS){ - await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams); - } + await DatabaseScripts.deleteRowsFromTableInDB(dbTableParams); await syncFeaturesApi(); } From 1caea1a74e08eef103be383ed797d7f203761450 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Mon, 9 Jun 2025 10:38:57 +0300 Subject: [PATCH 123/128] update imports --- tests/playwright/pageObjects/rdi-instances-list-page.ts | 2 +- tests/playwright/types/{databes.ts => databases.ts} | 0 tests/playwright/types/index.ts | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) rename tests/playwright/types/{databes.ts => databases.ts} (100%) diff --git a/tests/playwright/pageObjects/rdi-instances-list-page.ts b/tests/playwright/pageObjects/rdi-instances-list-page.ts index 42e11d4e94..efde53a000 100755 --- a/tests/playwright/pageObjects/rdi-instances-list-page.ts +++ b/tests/playwright/pageObjects/rdi-instances-list-page.ts @@ -2,7 +2,7 @@ import { Page, Locator, expect } from '@playwright/test' import { BaseOverviewPage } from './base-overview-page' import { AddRdiInstanceDialog } from './dialogs/add-rdi-instance-dialog' -import { RdiInstance } from '../types/rdi' +import { RdiInstance } from '../types' export class RdiInstancesListPage extends BaseOverviewPage { readonly AddRdiInstanceDialog: AddRdiInstanceDialog diff --git a/tests/playwright/types/databes.ts b/tests/playwright/types/databases.ts similarity index 100% rename from tests/playwright/types/databes.ts rename to tests/playwright/types/databases.ts diff --git a/tests/playwright/types/index.ts b/tests/playwright/types/index.ts index 86cc0bff92..e7a45e7df0 100644 --- a/tests/playwright/types/index.ts +++ b/tests/playwright/types/index.ts @@ -1,6 +1,7 @@ -export * from './databes' +export * from './databases' export * from './connections' export * from './keys' +export * from './rdi' declare global { interface Window { From 4d6e2039df4b6029d77a9f6633caa5cf3be638a6 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Mon, 9 Jun 2025 18:29:39 +0300 Subject: [PATCH 124/128] Detach E2E workflows (#4616) --- .github/workflows/tests-e2e.yml | 31 ++++++++----------------------- .github/workflows/tests.yml | 8 -------- 2 files changed, 8 insertions(+), 31 deletions(-) diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index 4b6fffe8d9..994727284f 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -11,27 +11,22 @@ on: default: false type: boolean - workflow_call: - inputs: - debug: - description: Enable SSH Debug - default: false - type: boolean - # Cancel a previous run workflow concurrency: group: ${{ github.workflow }}-${{ github.ref }}-e2e cancel-in-progress: true jobs: - # # E2E Approve + # E2E Approve e2e-approve: runs-on: ubuntu-latest - # TODO: uncomment when on push trigger is removed - # if: github.event.review.state == 'approved' || github.event_name == 'workflow_dispatch' + if: github.event.review.state == 'approved' && !contains(github.event.pull_request.labels.*.name, 'e2e-approved') || github.event_name == 'workflow_dispatch' name: Approve E2E tests steps: - - run: echo "Proceeding with E2E tests" + - name: Add "e2e-approved" Label + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: e2e-approved # E2E Docker build-docker: @@ -75,22 +70,12 @@ jobs: clean: uses: ./.github/workflows/clean-deployments.yml if: always() - needs: - [ - e2e-docker-tests, - e2e-appimage-tests, - tests-e2e-playwright, - ] + needs: [e2e-docker-tests, e2e-appimage-tests, tests-e2e-playwright] # Remove artifacts from github actions remove-artifacts: name: Remove artifacts - needs: - [ - e2e-docker-tests, - e2e-appimage-tests, - tests-e2e-playwright, - ] + needs: [e2e-docker-tests, e2e-appimage-tests, tests-e2e-playwright] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6c8fe7258d..e65264f9ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -117,14 +117,6 @@ jobs: resource_name: integration-coverage type: integration - # TODO: remove this after ✅ E2E Tests workflow is available in main - e2e-tests: - uses: ./.github/workflows/tests-e2e.yml - needs: changes - secrets: inherit - with: - debug: ${{ inputs.debug || false }} - clean: uses: ./.github/workflows/clean-deployments.yml if: always() From dadc265f26ea7fece473843b902c2373411548d5 Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Tue, 10 Jun 2025 10:04:19 +0300 Subject: [PATCH 125/128] update workflows --- .github/workflows/tests-e2e-approve.yml | 15 +++++++++++++++ .github/workflows/tests-e2e.yml | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/tests-e2e-approve.yml diff --git a/.github/workflows/tests-e2e-approve.yml b/.github/workflows/tests-e2e-approve.yml new file mode 100644 index 0000000000..8976a56040 --- /dev/null +++ b/.github/workflows/tests-e2e-approve.yml @@ -0,0 +1,15 @@ +name: ✅ E2E Approve + +on: + pull_request_review: + types: [submitted] + +jobs: + e2e-approve: + runs-on: ubuntu-latest + if: github.event.review.state == 'approved' + steps: + - name: Add "e2e-approved" Label + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: e2e-approved diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index 994727284f..00e293b03c 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -1,8 +1,8 @@ name: ✅ E2E Tests on: - pull_request_review: - types: [submitted] + pull_request: + types: [labeled] workflow_dispatch: inputs: @@ -20,7 +20,7 @@ jobs: # E2E Approve e2e-approve: runs-on: ubuntu-latest - if: github.event.review.state == 'approved' && !contains(github.event.pull_request.labels.*.name, 'e2e-approved') || github.event_name == 'workflow_dispatch' + if: github.event.action == 'labeled' && contains(github.event.label.name, 'e2e-approved') || github.event_name == 'workflow_dispatch' name: Approve E2E tests steps: - name: Add "e2e-approved" Label From c01b123638510146742b64a8a76d71dcca0d68e5 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 10 Jul 2025 09:22:21 +0300 Subject: [PATCH 126/128] Update README.md --- tests/playwright/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 5c629962a7..ec7c28262d 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -77,6 +77,8 @@ For more details, refer to the [Playwright documentation](https://playwright.dev ## Running Tests +*Note: Make sure to run the commands bellow from the `e2e/playwright` directory.* + ### Docker RI Build Run end-to-end tests in Chromium: ```shell From 16a940ae6ace36bf9db3e418b0f95fa482733b07 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Mon, 21 Jul 2025 08:44:10 +0300 Subject: [PATCH 127/128] feat: configure code coverage reports for the playwright e2e tests (#4722) re #RI-6570 --- package.json | 2 + redisinsight/ui/vite.config.mjs | 23 +- tests/playwright/.gitignore | 2 + tests/playwright/.nycrc.json | 19 + tests/playwright/README.md | 60 ++- tests/playwright/fixtures/test.ts | 63 ++- tests/playwright/package.json | 6 +- tests/playwright/yarn.lock | 808 +++++++++++++++++++++++++++++- yarn.lock | 59 ++- 9 files changed, 1012 insertions(+), 30 deletions(-) create mode 100644 tests/playwright/.nycrc.json diff --git a/package.json b/package.json index db35cc1c65..112f1fa018 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "private": true, "scripts": { "dev:ui": "cross-env yarn --cwd redisinsight/ui dev", + "dev:ui:coverage": "cross-env COLLECT_COVERAGE=true yarn --cwd redisinsight/ui dev", "dev:api": "cross-env yarn --cwd redisinsight/api start:dev", "dev:electron:ui": "cross-env RI_APP_PORT=8080 RI_APP_TYPE=ELECTRON NODE_ENV=development yarn --cwd redisinsight/ui dev", "dev:electron:api": "cross-env RI_APP_PORT=5540 RI_APP_TYPE=ELECTRON NODE_ENV=development USE_TCP_CLOUD_AUTH=true yarn --cwd redisinsight/api start:dev", @@ -222,6 +223,7 @@ "vite-plugin-ejs": "^1.7.0", "vite-plugin-electron": "^0.28.6", "vite-plugin-electron-renderer": "^0.14.5", + "vite-plugin-istanbul": "^7.1.0", "vite-plugin-react-click-to-component": "^3.0.0", "vite-plugin-svgr": "^4.2.0", "webpack": "^5.95.0", diff --git a/redisinsight/ui/vite.config.mjs b/redisinsight/ui/vite.config.mjs index 6047445739..81c5a3f6ac 100644 --- a/redisinsight/ui/vite.config.mjs +++ b/redisinsight/ui/vite.config.mjs @@ -5,6 +5,7 @@ import svgr from 'vite-plugin-svgr'; import fixReactVirtualized from 'esbuild-plugin-react-virtualized'; import { reactClickToComponent } from 'vite-plugin-react-click-to-component'; import { ViteEjsPlugin } from 'vite-plugin-ejs'; +import istanbul from 'vite-plugin-istanbul'; // import { compression } from 'vite-plugin-compression2' import { fileURLToPath, URL } from 'url'; import path from 'path'; @@ -47,8 +48,26 @@ export default defineConfig({ })};`; return html.replace(//, `\n ${script}`); - } - } + }, + }, + // Add istanbul plugin for coverage collection when COLLECT_COVERAGE is true + ...(process.env.COLLECT_COVERAGE === 'true' + ? [ + istanbul({ + include: 'src/**/*', + exclude: [ + 'node_modules', + 'test/', + '**/*.spec.ts', + '**/*.spec.tsx', + '**/*.test.ts', + '**/*.test.tsx', + ], + extension: ['.js', '.ts', '.tsx'], + requireEnv: false, + }), + ] + : []), // !isElectron && compression({ // include: [/\.(js)$/, /\.(css)$/], // deleteOriginalAssets: true diff --git a/tests/playwright/.gitignore b/tests/playwright/.gitignore index b7461d498c..ee4b77c6b7 100644 --- a/tests/playwright/.gitignore +++ b/tests/playwright/.gitignore @@ -6,3 +6,5 @@ node_modules/ /blob-report/ /playwright/.cache/ /allure-results/ +/.nyc_output/ +/coverage/ diff --git a/tests/playwright/.nycrc.json b/tests/playwright/.nycrc.json new file mode 100644 index 0000000000..553d10ef43 --- /dev/null +++ b/tests/playwright/.nycrc.json @@ -0,0 +1,19 @@ +{ + "all": true, + "cwd": "../../", + "include": ["redisinsight/ui/src/**/*.{ts,tsx,js,jsx}"], + "exclude": [ + "redisinsight/ui/src/**/*.{test,spec}.{ts,tsx,js,jsx}", + "redisinsight/ui/src/**/__tests__/**", + "redisinsight/ui/src/**/__mocks__/**", + "redisinsight/ui/src/**/*.d.ts", + "redisinsight/ui/src/**/node_modules/**" + ], + "reporter": ["text", "html", "lcov"], + "report-dir": "tests/playwright/coverage", + "temp-dir": "tests/playwright/.nyc_output", + "cache": false, + "check-coverage": false, + "skip-full": false, + "skip-empty": false +} diff --git a/tests/playwright/README.md b/tests/playwright/README.md index ec7c28262d..177f38aba1 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,6 +1,6 @@ # RedisInsight Playwright Tests -This project contains Playwright tests for RedisInsight. +This project contains Playwright tests for RedisInsight. It supports running tests on Electron, Docker and Web Redisinsight builds. ## Folder structure @@ -13,6 +13,7 @@ It supports running tests on Electron, Docker and Web Redisinsight builds. ## Prerequisites ### General Requirements + - Docker installed and running. - Redis test environment and RedisInsight configurations from the `tests/e2e` project. @@ -20,11 +21,13 @@ It supports running tests on Electron, Docker and Web Redisinsight builds. 1. **Start the Redis Test Environment** Navigate to the `tests/e2e` directory and run: + ```shell docker compose -p test-docker -f rte.docker-compose.yml up --force-recreate --detach ``` -2. **For Docker RI Build** +2. **For Docker RI Build** + - Build the Docker image locally or trigger a [GitHub Action](https://github.com/RedisInsight/RedisInsight/actions/workflows/manual-build.yml) to build and download the artifact (`docker-linux-alpine.amd64.tar`). - Load the image: ```shell @@ -40,14 +43,15 @@ It supports running tests on Electron, Docker and Web Redisinsight builds. ``` - Access the app at: `https://localhost:5540`. -3. **For Electron RI Build** +3. **For Electron RI Build** + - Build the project from the root directory: ```shell yarn package:prod ``` - Update `ELECTRON_EXECUTABLE_PATH` in `tests/playwright/env/.desktop.env` to point to the generated executable file (MacOS by default). -4. **For Local Web Build** +4. **For Local Web Build** - Stop the Docker RI container from step 2 (to free up port 5540). - Start the UI and API servers: ```shell @@ -59,11 +63,13 @@ It supports running tests on Electron, Docker and Web Redisinsight builds. ## Installation 1. Install dependencies: + ```shell yarn install ``` 2. Install Playwright browsers: + ```shell yarn playwright install ``` @@ -77,32 +83,40 @@ For more details, refer to the [Playwright documentation](https://playwright.dev ## Running Tests -*Note: Make sure to run the commands bellow from the `e2e/playwright` directory.* +_Note: Make sure to run the commands bellow from the `e2e/playwright` directory._ ### Docker RI Build + Run end-to-end tests in Chromium: + ```shell yarn test:chromium:docker ``` Run tests in debug mode: + ```shell yarn test:chromium:docker:debug ``` Run tests for a specific `.spec` file: + ```shell yarn test:chromium:docker basic-navigation ``` ### Electron RI Build + Run end-to-end tests for the Electron build: + ```shell yarn test:electron ``` ### Local Web Environment + Run tests for the local web environment: + ```shell yarn test:chromium:local-web ``` @@ -110,13 +124,17 @@ yarn test:chromium:local-web ## Extra Tooling ### Auto-Generate Tests + Use Playwright's Codegen to auto-generate tests: + ```shell yarn playwright codegen ``` ### Interactive UI Mode + Start Playwright's interactive UI mode: + ```shell yarn playwright test --ui ``` @@ -124,6 +142,7 @@ yarn playwright test --ui ## Reports ### Allure Reports + - Ensure `JAVA_HOME` is set and JDK version 8 to 11 is installed. - Generate a report with history: ```shell @@ -132,9 +151,40 @@ yarn playwright test --ui - For more details, refer to the [Allure documentation](https://allurereport.org/docs/playwright-reference/). ### Execution Time Comparison + | Test Name | Framework | Browser | Duration | | --------------------------------- | ---------- | -------- | -------- | | Verify that user can add Hash Key | TestCafe | Chromium | 27s | | Verify that user can add Hash Key | Playwright | Chromium | 10s | | Verify that user can add Hash Key | TestCafe | Electron | 30s | | Verify that user can add Hash Key | Playwright | Electron | 18s | + +## Code Coverage + +### Overview + +The Playwright tests can collect code coverage for the React frontend application. This helps track which parts of the UI code are being exercised by the end-to-end tests. + +### Quick Start + +# Start the UI with instrumentation for collecting code coverage + +Ensure UI app is running with `COLLECT_COVERAGE=true` env variable, or simply run the following helper from the root folder + +```shell +yarn dev:ui:coverage +``` + +# Run tests with coverage and generate both text and HTML reports + +```shell +cd tests/playwright +yarn test:coverage +``` + +### Coverage Reports Location + +After running coverage tests, reports are generated in: + +- **HTML Report**: `tests/playwright/coverage/index.html` - Interactive, browsable coverage report +- **LCOV Report**: `tests/playwright/coverage/lcov.info` - For CI/CD integration diff --git a/tests/playwright/fixtures/test.ts b/tests/playwright/fixtures/test.ts index 9a21b2c904..f604033b7b 100644 --- a/tests/playwright/fixtures/test.ts +++ b/tests/playwright/fixtures/test.ts @@ -7,14 +7,29 @@ import { _electron as electron, } from 'playwright' import log from 'node-color-log' - import { AxiosInstance } from 'axios' +import * as crypto from 'crypto' +import fs from 'fs' +import path from 'path' + import { apiUrl, isElectron, electronExecutablePath } from '../helpers/conf' import { generateApiClient } from '../helpers/api/http-client' import { APIKeyRequests } from '../helpers/api/api-keys' import { DatabaseAPIRequests } from '../helpers/api/api-databases' import { UserAgreementDialog } from '../pageObjects' +// Coverage type declaration +declare global { + interface Window { + // eslint-disable-next-line no-underscore-dangle + __coverage__: any + } +} + +export function generateUUID(): string { + return crypto.randomBytes(16).toString('hex') +} + type CommonFixtures = { forEachTest: void api: { @@ -25,6 +40,32 @@ type CommonFixtures = { } const commonTest = base.extend({ + // Simple context setup for coverage + context: async ({ context }, use) => { + if (process.env.COLLECT_COVERAGE === 'true') { + const outputDir = path.join(process.cwd(), '.nyc_output') + await fs.promises.mkdir(outputDir, { recursive: true }) + + // Expose coverage collection function + await context.exposeFunction( + 'collectIstanbulCoverage', + (coverageJSON: string) => { + if (coverageJSON) { + fs.writeFileSync( + path.join( + outputDir, + `playwright_coverage_${generateUUID()}.json`, + ), + coverageJSON, + ) + } + }, + ) + } + + await use(context) + }, + api: async ({ page }, use) => { const windowId = await page.evaluate(() => window.windowId) @@ -55,7 +96,25 @@ const commonTest = base.extend({ await use() - // after each test: + // Collect coverage after each test + if (process.env.COLLECT_COVERAGE === 'true') { + await page + .evaluate(() => { + if ( + typeof window !== 'undefined' && + // eslint-disable-next-line no-underscore-dangle + window.__coverage__ + ) { + ;(window as any).collectIstanbulCoverage( + // eslint-disable-next-line no-underscore-dangle + JSON.stringify(window.__coverage__), + ) + } + }) + .catch(() => { + // Ignore errors - page might be closed + }) + } }, { auto: true }, ], diff --git a/tests/playwright/package.json b/tests/playwright/package.json index 6933a03dd3..2eae9e1a7a 100644 --- a/tests/playwright/package.json +++ b/tests/playwright/package.json @@ -10,7 +10,8 @@ "allure-commandline": "^2.33.0", "allure-js-commons": "^3.2.0", "allure-playwright": "^3.2.0", - "cross-env": "^7.0.3" + "cross-env": "^7.0.3", + "nyc": "^17.1.0" }, "scripts": { "removeReportDirs": "rm -rf allure-results playwright-report test-results", @@ -22,6 +23,9 @@ "test:chromium:local-web:debug": "yarn test:chromium:local-web --debug", "test:electron": "cross-env envPath=env/.desktop.env yarn playwright test --project=Chromium", "test:electron:debug": "yarn test:electron --debug", + "test:coverage": "cross-env COLLECT_COVERAGE=true yarn playwright test; yarn coverage", + "coverage": "npx nyc report --reporter=html --reporter=lcov --reporter=text", + "coverage:clean": "rm -rf .nyc_output coverage", "clean:results": "rm -rf allure-results", "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock index 5965c8252c..372c760a93 100644 --- a/tests/playwright/yarn.lock +++ b/tests/playwright/yarn.lock @@ -2,6 +2,153 @@ # yarn lockfile v1 +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" + integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + +"@babel/core@^7.23.9": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" + integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.27.3" + "@babel/helpers" "^7.27.6" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.0" + "@babel/types" "^7.28.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" + integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== + dependencies: + "@babel/parser" "^7.28.0" + "@babel/types" "^7.28.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02" + integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.3" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.27.6": + version "7.27.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c" + integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.27.6" + +"@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" + integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== + dependencies: + "@babel/types" "^7.28.0" + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" + integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.0" + debug "^4.3.1" + +"@babel/types@^7.27.1", "@babel/types@^7.27.6", "@babel/types@^7.28.0": + version "7.28.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.1.tgz#2aaf3c10b31ba03a77ac84f52b3912a0edef4cf9" + integrity sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@faker-js/faker@^9.6.0": version "9.6.0" resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d" @@ -12,6 +159,48 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.12" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" + integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz#7358043433b2e5da569aa02cbc4c121da3af27d7" + integrity sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" + integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" @@ -98,11 +287,30 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + "aproba@^1.0.3 || ^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + are-we-there-yet@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" @@ -111,6 +319,13 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -159,6 +374,16 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +browserslist@^4.24.0: + version "4.25.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.1.tgz#ba9e8e6f298a1d86f829c9b975e07948967bb111" + integrity sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw== + dependencies: + caniuse-lite "^1.0.30001726" + electron-to-chromium "^1.5.173" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -191,6 +416,16 @@ cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" @@ -199,6 +434,16 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001726: + version "1.0.30001727" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" + integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== + charenc@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" @@ -219,6 +464,27 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" @@ -231,6 +497,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -241,6 +512,16 @@ console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cross-env@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" @@ -248,7 +529,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-spawn@^7.0.1, cross-spawn@^7.0.6: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -269,6 +550,18 @@ debug@4, debug@^4.3.3: dependencies: ms "^2.1.3" +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -281,6 +574,13 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +default-require-extensions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" + integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw== + dependencies: + strip-bom "^4.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -325,6 +625,11 @@ dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" +electron-to-chromium@^1.5.173: + version "1.5.182" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz#4ab73104f893938acb3ab9c28d7bec170c116b3e" + integrity sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -381,6 +686,21 @@ es-set-tostringtag@^2.1.0: has-tostringtag "^1.0.2" hasown "^2.0.2" +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -391,11 +711,44 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + follow-redirects@^1.15.6: version "1.15.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + +foreground-child@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + form-data@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae" @@ -407,6 +760,11 @@ form-data@^4.0.0: hasown "^2.0.2" mime-types "^2.1.12" +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -457,6 +815,16 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-intrinsic@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" @@ -473,6 +841,11 @@ get-intrinsic@^1.2.6: hasown "^2.0.2" math-intrinsics "^1.1.0" +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" @@ -486,7 +859,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== -glob@^7.1.3, glob@^7.1.4: +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -503,11 +876,16 @@ gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: +graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.3, has-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" @@ -525,6 +903,14 @@ has-unicode@^2.0.1: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -532,6 +918,11 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" @@ -629,16 +1020,115 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169" + integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.3" + istanbul-lib-coverage "^3.2.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^8.3.2" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -648,6 +1138,25 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -655,6 +1164,20 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-fetch-happen@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" @@ -708,7 +1231,7 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -837,6 +1360,18 @@ node-gyp@8.x: tar "^6.1.2" which "^2.0.2" +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -854,6 +1389,39 @@ npmlog@^6.0.0: gauge "^4.0.3" set-blocking "^2.0.0" +nyc@^17.1.0: + version "17.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-17.1.0.tgz#b6349a401a62ffeb912bd38ea9a018839fdb6eb1" + integrity sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^3.3.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^6.0.2" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + once@^1.3.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" @@ -861,6 +1429,27 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -868,6 +1457,26 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -878,6 +1487,18 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + playwright-core@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.52.0.tgz#238f1f0c3edd4ebba0434ce3f4401900319a3dca" @@ -910,6 +1531,13 @@ prebuild-install@^7.1.1: tar-fs "^2.0.0" tunnel-agent "^0.6.0" +process-on-spawn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.1.0.tgz#9d5999ba87b3bf0a8acb05322d69f2f5aa4fb763" + integrity sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q== + dependencies: + fromentries "^1.2.0" + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -955,12 +1583,34 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== + dependencies: + es6-error "^4.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -977,11 +1627,21 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +semver@^6.0.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + semver@^7.3.5: version "7.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== +semver@^7.5.3, semver@^7.5.4: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -999,11 +1659,16 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -1040,11 +1705,33 @@ socks@^2.6.2: ip-address "^9.0.5" smart-buffer "^4.2.0" +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + sprintf-js@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + sqlite3@^5.1.7: version "5.1.7" resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" @@ -1064,7 +1751,7 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1080,18 +1767,30 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + tar-fs@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5" @@ -1125,6 +1824,15 @@ tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -1132,6 +1840,18 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -1156,11 +1876,29 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -1175,12 +1913,66 @@ wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" diff --git a/yarn.lock b/yarn.lock index fec175a88b..a37c932b01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1465,7 +1465,7 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@istanbuljs/load-nyc-config@^1.0.0": +"@istanbuljs/load-nyc-config@^1.0.0", "@istanbuljs/load-nyc-config@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== @@ -3600,7 +3600,7 @@ acorn-import-attributes@^1.9.5: resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -3615,10 +3615,10 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.0.5, acorn@^8.1.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.0.4, acorn@^8.0.5, acorn@^8.1.0, acorn@^8.15.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -6543,6 +6543,11 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + eslint@^7.5.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -6589,6 +6594,15 @@ eslint@^7.5.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +espree@^10.3.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -7155,10 +7169,10 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.3.10: - version "10.4.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" - integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== +glob@^10.3.10, glob@^10.4.1: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -8321,7 +8335,7 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" -istanbul-lib-instrument@^6.0.0: +istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== @@ -10686,7 +10700,7 @@ pickleparser@^0.2.1: resolved "https://registry.yarnpkg.com/pickleparser/-/pickleparser-0.2.1.tgz#7a03f1e9204e91ec9b8efbd3ba2f1eb5955b994d" integrity sha512-kMzY3uFYcR6OjOqr7nV2nkaXaBsUEOafu3zgPxeD6s/2ueMfVQH8lrymcDWBPGx0OkVxGMikxQit6jgByXjwBg== -picocolors@^1.0.0, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -13061,6 +13075,15 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +test-exclude@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2" + integrity sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^10.4.1" + minimatch "^9.0.4" + text-diff@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/text-diff/-/text-diff-1.0.1.tgz#6c105905435e337857375c9d2f6ca63e453ff565" @@ -13893,6 +13916,18 @@ vite-plugin-electron@^0.28.6: resolved "https://registry.yarnpkg.com/vite-plugin-electron/-/vite-plugin-electron-0.28.6.tgz#98bcf291179dfbdfef407f881cbb1e1d58249c57" integrity sha512-DANntooA/XcUQuaOG7tQ0nnWh8iP5yKur2e9GDafjslOPAVZehRyrbi2UEI6rlIhN6hHwcqAjY+/Zz8+thAL5g== +vite-plugin-istanbul@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/vite-plugin-istanbul/-/vite-plugin-istanbul-7.1.0.tgz#f1abd43d38cb094a1dae9d383b28e66a11141f91" + integrity sha512-md0774bPYfSrMbAMMy3Xui2+xqmEVwulCGN2ImGm4E4s+0VfO7TjFyJ4ITFIFyEmBhWoMM0sOOX0Yg0I1SsncQ== + dependencies: + "@istanbuljs/load-nyc-config" "^1.1.0" + espree "^10.3.0" + istanbul-lib-instrument "^6.0.3" + picocolors "^1.1.1" + source-map "^0.7.4" + test-exclude "^7.0.1" + vite-plugin-react-click-to-component@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/vite-plugin-react-click-to-component/-/vite-plugin-react-click-to-component-3.0.0.tgz#bd22d0210ca245bf00b513ad4f6f28065cc98880" From df9ac588f21a6a2cbf5859a87040d488bf26bfee Mon Sep 17 00:00:00 2001 From: Pavel Angelov Date: Wed, 23 Jul 2025 16:21:48 +0300 Subject: [PATCH 128/128] restructure readme --- tests/playwright/README.md | 148 ++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 68 deletions(-) diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 177f38aba1..19897b5276 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,68 +1,20 @@ # RedisInsight Playwright Tests -This project contains Playwright tests for RedisInsight. -It supports running tests on Electron, Docker and Web Redisinsight builds. +This project contains end-to-end tests for RedisInsight using [Playwright](https://playwright.dev/). It supports running tests against three different RedisInsight builds: -## Folder structure - -- `/env` - contains env configs for the 3 types of builds. -- `/tests` - Contains the actual tests. -- `/helpers/api` - ported some api helpers from the tests/e2e project. They are used for setting up data. -- `/pageObjects` - ported page element locators and logic from the tests/e2e project. - -## Prerequisites - -### General Requirements +- **Docker Build** +- **Electron Build** +- **Local Web Build** (built directly from the source code) -- Docker installed and running. -- Redis test environment and RedisInsight configurations from the `tests/e2e` project. +--- -### Steps to Set Up - -1. **Start the Redis Test Environment** - Navigate to the `tests/e2e` directory and run: - - ```shell - docker compose -p test-docker -f rte.docker-compose.yml up --force-recreate --detach - ``` +## Installation -2. **For Docker RI Build** - - - Build the Docker image locally or trigger a [GitHub Action](https://github.com/RedisInsight/RedisInsight/actions/workflows/manual-build.yml) to build and download the artifact (`docker-linux-alpine.amd64.tar`). - - Load the image: - ```shell - docker load -i docker-linux-alpine.amd64.tar - ``` - - Ensure the following environment variables are set in `tests/e2e/.env`: - - `RI_ENCRYPTION_KEY` - - `RI_SERVER_TLS_CERT` - - `RI_SERVER_TLS_KEY` - - Navigate to the `tests/e2e` directory and start the container: - ```shell - docker compose -p e2e-ri-docker -f docker.web.docker-compose.yml up --detach --force-recreate - ``` - - Access the app at: `https://localhost:5540`. - -3. **For Electron RI Build** - - - Build the project from the root directory: - ```shell - yarn package:prod - ``` - - Update `ELECTRON_EXECUTABLE_PATH` in `tests/playwright/env/.desktop.env` to point to the generated executable file (MacOS by default). - -4. **For Local Web Build** - - Stop the Docker RI container from step 2 (to free up port 5540). - - Start the UI and API servers: - ```shell - yarn dev:ui - yarn dev:api - ``` - - Access the app at: `http://localhost:8080`. +> _Note: All commands below should be run from the `tests/playwright` directory._ -## Installation +Before running any tests, make sure you have the dependencies installed: -1. Install dependencies: +1. Install Node dependencies: ```shell yarn install @@ -74,53 +26,113 @@ It supports running tests on Electron, Docker and Web Redisinsight builds. yarn playwright install ``` -3. Install Playwright OS dependencies (requires `sudo`): +3. Install Playwright OS dependencies (Linux only): + ```shell sudo yarn playwright install-deps ``` +## Prerequisites + +- Docker installed and running. +- Redis test environment and RedisInsight configurations from the `tests/e2e` project. + +## Environment-Specific Setup and Test Execution + For more details, refer to the [Playwright documentation](https://playwright.dev/docs/running-tests). -## Running Tests +### Start Redis Test Environment (Required for All Builds) -_Note: Make sure to run the commands bellow from the `e2e/playwright` directory._ +Navigate to the `tests/e2e` directory and run: + +```shell +docker compose -p test-docker -f rte.docker-compose.yml up --force-recreate --detach +``` + +### Docker Build + +- Build the Docker image locally or trigger a [GitHub Action](https://github.com/RedisInsight/RedisInsight/actions/workflows/manual-build.yml) to build and download the artifact (`docker-linux-alpine.amd64.tar`). +- Load the image: + ```shell + docker load -i docker-linux-alpine.amd64.tar + ``` +- Ensure the following environment variables are set in `tests/e2e/.env`: + - `RI_ENCRYPTION_KEY` + - `RI_SERVER_TLS_CERT` + - `RI_SERVER_TLS_KEY` +- Navigate to the `tests/e2e` directory and start the container: + ```shell + docker compose -p e2e-ri-docker -f docker.web.docker-compose.yml up --detach --force-recreate + ``` +- Validate app is running at: `https://localhost:5540`. -### Docker RI Build +#### Run Playwright Tests -Run end-to-end tests in Chromium: +_Note: Make sure to run the commands bellow from the `e2e/playwright` directory._ + +Run all tests: ```shell yarn test:chromium:docker ``` -Run tests in debug mode: +Run in debug mode: ```shell yarn test:chromium:docker:debug ``` -Run tests for a specific `.spec` file: +Run a specific spec file: ```shell yarn test:chromium:docker basic-navigation ``` -### Electron RI Build +--- -Run end-to-end tests for the Electron build: +### Electron Build + +- Build the project from the root directory: + ```shell + yarn package:prod + ``` +- Update `ELECTRON_EXECUTABLE_PATH` in `tests/playwright/env/.desktop.env` to point to the generated executable file (MacOS by default). + +#### Run Playwright Tests + +_Note: Make sure to run the commands bellow from the `e2e/playwright` directory._ ```shell yarn test:electron ``` -### Local Web Environment +--- + +### Local Web Build -Run tests for the local web environment: +- Make sure you don't have anything (docker container, local server, etc.) running on port 5540. +- Start the UI and API servers: + ```shell + yarn dev:ui + yarn dev:api + ``` +- Access the app at: `http://localhost:8080`. + +#### Run Playwright Tests + +_Note: Make sure to run the command bellow from the `e2e/playwright` directory._ ```shell yarn test:chromium:local-web ``` +## Folder structure + +- `/env` - contains env configs for the 3 types of builds. +- `/tests` - Contains the actual tests. +- `/helpers/api` - ported some api helpers from the tests/e2e project. They are used for setting up data. +- `/pageObjects` - ported page element locators and logic from the tests/e2e project. + ## Extra Tooling ### Auto-Generate Tests