[ACS-10956] Update eslint configuration for all automated tests related files (#4991)

* [ACS-10956] ACS-10956 updates eslint for more files

* [ACS-10956] eslint update

* Revert package-lock.json changes

* Revert package-lock.json to develop version

* testing eslint changes 1

* [ACS-10956] updated eslint for e2e folder

* [ACS-10956] added eslint for aca-playwright-shared file

* Sync package-lock.json with develop

* [ACS-10956] eslint e2e now uses playwright and angular plugin.

* [ACS-10956] changed error to warn for no-angular-material-selectors

* [ACS-10956] two rules turned off for aca-playwright-shared

* [ACS-10956] reverted changes for spec files

* [ACS-10956] changed back to error

* [ACS-10956] first eslint fixes

* [ACS-10956] more eslint fixes

* [ACS-10956] more eslint fixes pt 23

* [ACS-10956] last lintfix fixes

* [ACS-10956] sonar fixes 1

* [ACS-10956] reverted package-lock.json

* [ACS-10956] test fixes after the eslint changes 1

* [ACS-10956] sonar fix2

* [ACS-10956] test fixes after eslint update 24
This commit is contained in:
Adam Świderski
2026-01-30 15:23:48 +01:00
committed by GitHub
parent bf367628ae
commit 19270e256c
61 changed files with 188 additions and 281 deletions
+37
View File
@@ -0,0 +1,37 @@
{
"extends": ["plugin:playwright/playwright-test"],
"plugins": ["@alfresco/eslint-angular"],
"rules": {
"@typescript-eslint/no-floating-promises": "warn",
"@typescript-eslint/no-misused-promises": "warn",
"max-lines": "off",
"@alfresco/eslint-angular/no-angular-material-selectors": "error",
"no-empty-pattern": "off",
"playwright/expect-expect": "off",
"playwright/max-nested-describe": "off",
"playwright/missing-playwright-await": "error",
"playwright/no-conditional-expect": "off",
"playwright/no-conditional-in-test": "off",
"playwright/no-duplicate-hooks": "error",
"playwright/no-element-handle": "warn",
"playwright/no-eval": "warn",
"playwright/no-focused-test": "error",
"playwright/no-force-option": "warn",
"playwright/no-nested-step": "warn",
"playwright/no-networkidle": "warn",
"playwright/no-page-pause": "error",
"playwright/no-raw-locators": "warn",
"playwright/no-skipped-test": "error",
"playwright/no-standalone-expect": "error",
"playwright/no-unsafe-references": "warn",
"playwright/no-useless-await": "warn",
"playwright/no-useless-not": "warn",
"playwright/no-wait-for-selector": "warn",
"playwright/no-wait-for-timeout": "off",
"playwright/prefer-web-first-assertions": "off",
"playwright/valid-describe-callback": "warn",
"playwright/valid-expect": "warn",
"playwright/valid-expect-in-promise": "off",
"playwright/valid-title": "off"
}
}
+1 -34
View File
@@ -323,7 +323,7 @@
}
},
{
"files": ["*.spec.ts", "*.test.ts", "*.e2e.ts"],
"files": ["*.spec.ts"],
"plugins": ["@alfresco/eslint-angular"],
"rules": {
"@typescript-eslint/no-floating-promises": "warn",
@@ -332,39 +332,6 @@
"@alfresco/eslint-angular/no-angular-material-selectors": "error"
}
},
{
"files": ["*.e2e.ts"],
"extends": ["plugin:playwright/playwright-test"],
"rules": {
"no-empty-pattern": "off",
"playwright/expect-expect": "off",
"playwright/max-nested-describe": "off",
"playwright/missing-playwright-await": "error",
"playwright/no-conditional-expect": "off",
"playwright/no-conditional-in-test": "off",
"playwright/no-duplicate-hooks": "error",
"playwright/no-element-handle": "warn",
"playwright/no-eval": "warn",
"playwright/no-focused-test": "error",
"playwright/no-force-option": "warn",
"playwright/no-nested-step": "warn",
"playwright/no-networkidle": "warn",
"playwright/no-page-pause": "error",
"playwright/no-raw-locators": "warn",
"playwright/no-skipped-test": "error",
"playwright/no-standalone-expect": "error",
"playwright/no-unsafe-references": "warn",
"playwright/no-useless-await": "warn",
"playwright/no-useless-not": "warn",
"playwright/no-wait-for-selector": "warn",
"playwright/no-wait-for-timeout": "off",
"playwright/prefer-web-first-assertions": "off",
"playwright/valid-describe-callback": "warn",
"playwright/valid-expect": "warn",
"playwright/valid-expect-in-promise": "off",
"playwright/valid-title": "off"
}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/authentication/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/copy-move-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/create-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/delete-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/edit-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/favorite-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/folder-information-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/folder-rules/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -26,6 +26,7 @@ import { expect } from '@playwright/test';
import { ActionType, ApiClientFactory, test, Utils, TrashcanApi, NodesApi, Comparator, Field } from '@alfresco/aca-playwright-shared';
test.use({ launchOptions: { slowMo: 300 } });
test.describe('Folder Rules Actions', () => {
const apiClientFactory = new ApiClientFactory();
let nodesApi: NodesApi;
@@ -26,6 +26,7 @@ import { ApiClientFactory, test, Utils, NodesApi, TrashcanApi, RulesApi } from '
import { expect } from '@playwright/test';
test.use({ launchOptions: { slowMo: 300 } });
test.describe('Rules - Manage Rules', () => {
const apiClientFactory = new ApiClientFactory();
let nodesApi: NodesApi;
@@ -106,6 +106,7 @@ async function verifyTransformation(
}
test.use({ launchOptions: { slowMo: 300 } });
test.describe('Folder Rules Actions', () => {
const apiClientFactory = new ApiClientFactory();
let nodesApi: NodesApi;
@@ -26,6 +26,7 @@ import { ApiClientFactory, test, Utils, NodesApi, TrashcanApi, ActionType, Rules
import { expect } from '@playwright/test';
test.use({ launchOptions: { slowMo: 300 } });
test.describe('Rules - Manage Rules', () => {
const apiClientFactory = new ApiClientFactory();
let nodesApi: NodesApi;
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/info-drawer/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -195,8 +195,8 @@ test.describe('Library properties', () => {
await myLibrariesPage.libraryDetails.editButton.click();
await myLibrariesPage.libraryDetails.nameField.fill(Utils.string257Long);
await expect(myLibrariesPage.libraryDetails.errorMessage).toBeVisible();
expect((await myLibrariesPage.libraryDetails.errorMessage.textContent()).trim()).toEqual('Use 256 characters or less for title');
await expect(myLibrariesPage.libraryDetails.errorNameMessage).toBeVisible();
expect((await myLibrariesPage.libraryDetails.errorNameMessage.textContent()).trim()).toEqual('Use 256 characters or less for title');
await expect(myLibrariesPage.libraryDetails.updateButton).toBeDisabled();
});
@@ -210,8 +210,8 @@ test.describe('Library properties', () => {
await myLibrariesPage.libraryDetails.editButton.click();
await myLibrariesPage.libraryDetails.descriptionField.fill(Utils.string513Long);
await expect(myLibrariesPage.libraryDetails.errorMessage).toBeVisible();
expect((await myLibrariesPage.libraryDetails.errorMessage.textContent()).trim()).toEqual('Use 512 characters or less for description');
await expect(myLibrariesPage.libraryDetails.errorDescriptionMessage).toBeVisible();
expect((await myLibrariesPage.libraryDetails.errorDescriptionMessage.textContent()).trim()).toEqual('Use 512 characters or less for description');
await expect(myLibrariesPage.libraryDetails.updateButton).toBeDisabled();
});
});
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/library-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/list-views/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -40,6 +40,7 @@ import { Site } from '@alfresco/js-api';
test.describe('Special permissions', () => {
const username = `userPermissions-${Utils.random()}`;
let siteApiAdmin: SitesApi;
test.beforeAll(async () => {
const apiClientFactory = new ApiClientFactory();
await apiClientFactory.setUpAcaBackend('admin');
@@ -240,6 +240,7 @@ test.describe('Remember sorting', () => {
test.describe('User Tests', () => {
test.describe.configure({ mode: 'serial' });
test('[XAT-4520] Size sort order is retained when user logs out and logs back in', async ({ personalFiles, loginPage }) => {
await personalFiles.dataTable.sortBy('Name', 'desc');
await personalFiles.dataTable.spinnerWaitForReload();
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/navigation/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
+2 -3
View File
@@ -4,11 +4,10 @@
"overrides": [
{
"files": ["*.ts"],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": ["e2e/playwright/pagination/tsconfig.e2e.json"],
"createDefaultProgram": true
"project": ["e2e/playwright/pagination/tsconfig.e2e.json"]
},
"plugins": ["rxjs", "unicorn"],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -68,6 +68,7 @@ test.describe('Pagination on multiple pages : ', () => {
test.beforeAll(async () => {
await favoritesApi.waitForApi(username, { expect: initialFavoritesTotalItems + 51 });
});
favoritesTests(username);
});
});
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/search/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -26,6 +26,7 @@ import { expect } from '@playwright/test';
import { ApiClientFactory, Utils, test, NodesApi, TrashcanApi, FileActionsApi, TEST_FILES } from '@alfresco/aca-playwright-shared';
test.use({ launchOptions: { slowMo: 500 } });
test.describe('Search - Filters - Date', () => {
let nodesApi: NodesApi;
let trashcanApi: TrashcanApi;
@@ -26,6 +26,7 @@ import { expect } from '@playwright/test';
import { ApiClientFactory, Utils, test, NodesApi, TrashcanApi, TEST_FILES, FileActionsApi } from '@alfresco/aca-playwright-shared';
test.use({ launchOptions: { slowMo: 500 } });
test.describe('Search Highlighting', () => {
let nodesApi: NodesApi;
let trashcanApi: TrashcanApi;
@@ -37,6 +37,7 @@ import {
} from '@alfresco/aca-playwright-shared';
test.use({ launchOptions: { slowMo: 500 } });
test.describe('Search sorting', () => {
const random = Utils.random();
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/share-action/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -182,6 +182,10 @@ test.describe('Share a file', () => {
await personalFiles.shareDialog.expireToggle.click();
expect(await personalFiles.shareDialog.isExpireToggleEnabled()).toBe(true);
await personalFiles.shareDialog.expireInput.click();
await personalFiles.shareDialog.clockIcon.click();
await expect(personalFiles.shareDialog.dateErrorText).toContainText('Invalid date');
await personalFiles.shareDialog.datetimePickerButton.click();
expect(await personalFiles.shareDialog.dateTimePicker.isCalendarOpen()).toBe(true);
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/smoke-test/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/special-permissions-actions-available/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -32,18 +32,6 @@ test.describe('Folders - available actions : ', () => {
let trashcanApi: TrashcanApi;
async function checkActionsAvailable(
myPersonalFiles: PersonalFilesPage,
item: string,
expectedToolbarPrimary: string[],
expectedToolbarMore: string[]
): Promise<void> {
await myPersonalFiles.dataTable.selectItems(item);
await myPersonalFiles.acaHeader.verifyToolbarPrimaryActions(expectedToolbarPrimary);
await myPersonalFiles.acaHeader.clickMoreActions();
await myPersonalFiles.matMenu.verifyActualMoreActions(expectedToolbarMore);
}
async function checkMultipleSelActionsAvailable(
myPersonalFiles: PersonalFilesPage,
expectedToolbarPrimary: string[],
expectedToolbarMore: string[]
@@ -84,18 +72,13 @@ test.describe('Folders - available actions : ', () => {
test('[XAT-4637] Actions for a folder, not favorite', async ({ personalFiles }) => {
await personalFiles.dataTable.getRowByName(testData.folderFile.name).click({ button: 'right' });
await personalFiles.matMenu.verifyActualMoreActions(testData.folderFile.contextMenu);
await checkActionsAvailable(personalFiles, testData.folderFile.name, testData.folderFile.toolbarPrimary, testData.folderFile.toolbarMore);
await checkActionsAvailable(personalFiles, testData.folderFile.toolbarPrimary, testData.folderFile.toolbarMore);
});
test('[XAT-4638] Actions for a folder, favorite', async ({ personalFiles }) => {
await personalFiles.dataTable.getRowByName(testData.folderFavFile.name).click({ button: 'right' });
await personalFiles.matMenu.verifyActualMoreActions(testData.folderFavFile.contextMenu);
await checkActionsAvailable(
personalFiles,
testData.folderFavFile.name,
testData.folderFavFile.toolbarPrimary,
testData.folderFavFile.toolbarMore
);
await checkActionsAvailable(personalFiles, testData.folderFavFile.toolbarPrimary, testData.folderFavFile.toolbarMore);
});
test('[XAT-4642] Actions on multiple folders', async ({ personalFiles }) => {
@@ -103,14 +86,14 @@ test.describe('Folders - available actions : ', () => {
await personalFiles.dataTable.getRowByName(testData.folderFavFile.name).click({ button: 'right' });
await personalFiles.page.reload({ waitUntil: 'load' });
await personalFiles.dataTable.selectItems(testData.folderFavFile.name, testData.folderFile.name);
await checkMultipleSelActionsAvailable(personalFiles, testData.multipleSelFile.toolbarPrimary, testData.multipleSelFile.toolbarMore);
await checkActionsAvailable(personalFiles, testData.multipleSelFile.toolbarPrimary, testData.multipleSelFile.toolbarMore);
});
test('[XAT-4643] Actions on multiple selection - files and folders', async ({ personalFiles }) => {
await personalFiles.dataTable.selectItems(testData.file.name, testData.folderFile.name);
await personalFiles.dataTable.getRowByName(testData.folderFile.name).click({ button: 'right' });
await personalFiles.matMenu.verifyActualMoreActions(testData.multipleSelFile.contextMenu);
await checkMultipleSelActionsAvailable(personalFiles, testData.multipleSelFile.toolbarPrimary, testData.multipleSelFile.toolbarMore);
await checkActionsAvailable(personalFiles, testData.multipleSelFile.toolbarPrimary, testData.multipleSelFile.toolbarMore);
});
});
});
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/upload-download-actions/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
+2 -6
View File
@@ -8,16 +8,12 @@
"files": [
"*.ts"
],
"extends": ["../../../.eslintrc.e2e.json"],
"parserOptions": {
"project": [
"e2e/playwright/viewer/tsconfig.e2e.json"
],
"createDefaultProgram": true
]
},
"plugins": [
"rxjs",
"unicorn"
],
"rules": {
"@typescript-eslint/no-floating-promises": "off"
}
@@ -26,6 +26,7 @@ import { expect } from '@playwright/test';
import { ApiClientFactory, getUserState, test, TEST_FILES, timeouts, Utils } from '@alfresco/aca-playwright-shared';
test.use({ storageState: getUserState('hruser') });
test.describe('viewer action file', () => {
const apiClientFactory = new ApiClientFactory();
const randomFolderName = `viewer-action-${Utils.random()}`;
@@ -36,6 +36,7 @@ import {
} from '@alfresco/aca-playwright-shared';
test.use({ channel: 'chrome' });
test.describe('viewer file types', () => {
const randomString = Utils.random();
const username = `user-${randomString}`;
@@ -26,6 +26,7 @@ import { expect } from '@playwright/test';
import { ApiClientFactory, getUserState, test, TEST_FILES, Utils } from '@alfresco/aca-playwright-shared';
test.use({ storageState: getUserState('hruser') });
test.describe('viewer file', () => {
const apiClientFactory = new ApiClientFactory();
const randomFolderName = `viewer-protected-${Utils.random()}`;
@@ -14,9 +14,9 @@
[errorStateMatcher]="matcher"
/>
@if (libraryTitleExists) {
<mat-hint>{{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}</mat-hint>
<mat-hint data-automation-id="app-library-metadata-form-name-hint">{{ 'LIBRARY.HINTS.SITE_TITLE_EXISTS' | translate }}</mat-hint>
}
<mat-error>
<mat-error data-automation-id="app-library-metadata-form-name-error">
{{ titleErrorTranslationKey | translate }}
</mat-error>
</mat-form-field>
+10 -2
View File
@@ -1,6 +1,6 @@
const path = require('path');
module.exports = {
extends: '../../.eslintrc.json',
extends: ['../../.eslintrc.json', '../../.eslintrc.e2e.json'],
ignorePatterns: ['!**/*'],
overrides: [
{
@@ -10,7 +10,15 @@ module.exports = {
createDefaultProgram: true
},
rules: {
'no-console': ['error', {'allow': ['info', 'log', 'warn', 'error'] }]
'no-console': ['error', {'allow': ['info', 'log', 'warn', 'error'] }],
'playwright/no-raw-locators': 'off'
}
},
{
files: ['*.js'],
rules: {
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off'
}
}
]
@@ -62,21 +62,19 @@ export enum MimeType {
export class ActionsDropdownComponent extends BaseComponent {
private static rootElement = 'aca-edit-rule-dialog aca-rule-action-list';
private getOptionLocator = (optionName: string): Locator =>
this.page.locator('.mat-mdc-select-panel .mdc-list-item__primary-text', { hasText: optionName }).first();
private getOptionLocator = (optionName: string): Locator => this.page.locator('[role=listbox] [role=option]', { hasText: optionName }).first();
private ruleActionLocator = this.getChild('aca-rule-action');
private addActionButtonLocator = this.getChild('[data-automation-id="rule-action-list-add-action-button"]');
private actionDropdownLocator = this.getChild('[data-automation-id="rule-action-select"]');
private actionAspectNameLocator = '[data-automation-id="header-aspect-name"] .adf-property-field';
private actionCheckInInputLocator = '[data-automation-id="header-description"] input';
private actionAutoDeclareLocator = '[data-automation-id="header-version"] mat-select';
private actionSpecialiseTypeLocator = '[data-automation-id="header-type-name"] mat-select';
private actionSimpleWorkflowStepInputLocator = '[data-automation-id="header-approve-step"] input';
private actionSimpleWorkflowApproveFolderLocator = `[data-automation-id="header-approve-folder"] mat-icon`;
private actionSimpleWorkflowActionChoiceLocator = '[data-automation-id="content-node-selector-actions-choose"]';
private actionSimpleWorkflowLabelApproveLocator = `[data-automation-id="card-boolean-label-approve-move"]`;
private actionSimpleWorkflowSRejectStepLocator = '[data-automation-id="header-reject-step"] input';
private actionSimpleWorkflowRejectFolderLocator = `[data-automation-id="header-reject-folder"] input`;
private readonly actionSimpleWorkflowApproveFolderLocator = `[data-automation-id="header-approve-folder"] [role="img"]`;
private readonly actionSpecialiseTypeLocator = '[data-automation-id="header-type-name"] [role="combobox"]';
private readonly mimeTypeDropdownLocator = this.getChild('[data-automation-id="select-box"][aria-label="Mimetype *"]');
private readonly actionTransformAndCopyContentDestinationFolderLocator = '[data-automation-id="card-textitem-value-destination-folder"]';
private readonly contentNodeSelectorSearchInput = '[data-automation-id="content-node-selector-search-input"]';
@@ -108,10 +106,6 @@ export class ActionsDropdownComponent extends BaseComponent {
await this.dropdownSelection(AspectValue, this.actionAspectNameLocator, index);
}
async insertAutoDeclareOptionsActionValues(autoDeclareOptionsValue: string, index: number): Promise<void> {
await this.dropdownSelection(autoDeclareOptionsValue, this.actionAutoDeclareLocator, index);
}
async insertSpecialiseTypeActionValues(specialiseTypeValue: string, index: number): Promise<void> {
await this.dropdownSelection(specialiseTypeValue, this.actionSpecialiseTypeLocator, index);
}
@@ -39,7 +39,7 @@ export class AdfInfoDrawerComponent extends BaseComponent {
this.getChild(`[data-automation-id="library-name-properties-wrapper"] input[placeholder='${labelText}']`);
public getIdField = (labelText: string) => this.getChild(`[data-automation-id="library-id-properties-wrapper"] input[placeholder='${labelText}']`);
public getVisibilityField = (labelText: string) =>
this.getChild(`[data-automation-id="library-visibility-properties-wrapper"] mat-select[ng-reflect-placeholder='${labelText}']`);
this.getChild(`[data-automation-id="library-visibility-properties-wrapper"] [role="combobox"][ng-reflect-placeholder='${labelText}']`);
public getDescriptionField = this.getChild('[data-automation-id="library-description-properties-wrapper"] textarea');
public propertiesTab = this.page.getByRole('tab', { name: 'Properties' });
public commentsTab = this.page.getByRole('tab', { name: 'Comments' });
@@ -56,10 +56,11 @@ export class AdfInfoDrawerComponent extends BaseComponent {
public editButton = this.page.getByRole('button', { name: 'Edit' });
public cancelButton = this.page.getByRole('button', { name: 'Cancel' });
public updateButton = this.page.getByRole('button', { name: 'Update' });
public hintMessage = this.page.locator('mat-hint');
public errorMessage = this.page.locator('mat-error');
public hintMessage = this.getChild('[data-automation-id="app-library-metadata-form-name-hint"]');
public errorNameMessage = this.getChild('[data-automation-id="library-name-properties-wrapper"] [aria-atomic="true"]');
public errorDescriptionMessage = this.getChild('[data-automation-id="library-description-properties-wrapper"] [aria-atomic="true"]');
public expandDetailsButton = this.getChild(`button[title='Expand panel']`);
public expandedDetailsTabs = this.page.locator('.aca-details-container .mdc-tab__text-label');
public expandedDetailsTabs = this.page.locator('.aca-details-container [role="tab"]');
public expandedDetailsPermissionsTab = this.expandedDetailsTabs.getByText('Permissions');
public nameField = this.page.locator('input[placeholder=Name]');
public idField = this.page.locator(`input[placeholder='Library ID']`);
@@ -72,7 +73,7 @@ export class AdfInfoDrawerComponent extends BaseComponent {
public categoriesAccordionPenButton = this.categoriesAccordion.locator('[data-automation-id="meta-data-categories-edit"]');
public tagsInput = this.tagsCreator.locator('input');
public createTagButton = this.tagsCreator.locator('.adf-create-tag-label');
public tagsChips = this.tagsCreator.locator('mat-chip');
public tagsChips = this.tagsCreator.locator('[role="listitem"]');
public tagsChipsXButton = this.tagsChips.locator('.adf-dynamic-chip-list-delete-icon');
public tagsAccordionCancelButton = this.getChild('[data-automation-id="reset-tags-metadata"]');
public tagsAccordionConfirmButton = this.getChild('[data-automation-id="save-tags-metadata"]');
@@ -29,7 +29,7 @@ import { timeouts } from '../../utils';
export abstract class BaseComponent extends PlaywrightBase {
private readonly rootElement: string;
private overlayElement = this.page.locator('.cdk-overlay-backdrop-showing');
private progressBar = this.page.locator('mat-progress-bar');
private progressBar = this.page.locator('[role="progressbar"]');
protected constructor(page: Page, rootElement: string) {
super(page);
@@ -57,8 +57,8 @@ export abstract class BaseComponent extends PlaywrightBase {
async spinnerWaitForReload(): Promise<void> {
try {
await this.page.locator('mat-progress-spinner').waitFor({ state: 'attached', timeout: timeouts.medium });
await this.page.locator('mat-progress-spinner').waitFor({ state: 'detached', timeout: timeouts.normal });
await this.page.locator('[role="progressbar"]').waitFor({ state: 'attached', timeout: timeouts.medium });
await this.page.locator('[role="progressbar"]').waitFor({ state: 'detached', timeout: timeouts.normal });
} catch (e) {
this.logger.info('Spinner was not present');
}
@@ -43,7 +43,7 @@ export enum Comparator {
}
export class ConditionComponent extends ManageRulesDialogComponent {
private getOptionLocator = (optionName: string): Locator => this.page.locator(`.cdk-overlay-pane .mat-mdc-option span`, { hasText: optionName });
private getOptionLocator = (optionName: string): Locator => this.page.locator('[role=listbox] [role=option]', { hasText: optionName }).first();
private async selectField(fields: Partial<Field>, index: number): Promise<void> {
await this.fieldDropDown.nth(index).click();
@@ -56,8 +56,6 @@ export class DataTableComponent extends BaseComponent {
sitesName = this.page.locator('.adf-datatable-body [data-automation-id*="datatable-row"] [aria-label="Name"]');
sitesRole = this.page.locator('.adf-datatable-body [data-automation-id*="datatable-row"] [aria-label="My Role"]');
lockOwner = this.page.locator('.aca-locked-by--name');
uncheckedCheckbox = this.page.locator('.mat-mdc-checkbox');
checkedCheckbox = this.page.locator('.mat-mdc-checkbox-checked');
highlightedText = '.aca-highlight';
searchFileName = '.search-file-name';
searchFileDescription = '[data-automation-id="search-results-entry-description"]';
@@ -107,14 +105,6 @@ export class DataTableComponent extends BaseComponent {
*/
getByAriaLabelTitle = (title: string): Locator => this.getChild(`[aria-label="${title}"]`);
/**
* Method used in cases where we want to get the button (hamburger menu) for row element
*
* @returns reference to menu placed in row localized by the name
*/
getActionsButtonByName = (name: string): Locator =>
this.getRowByName(name).locator('mat-icon', { hasText: new RegExp(`^\\s*more_vert\\s*$`, 'g') });
/**
* Method used in cases where we want to get the edit button and there is no hamburger menu
*
@@ -142,7 +132,7 @@ export class DataTableComponent extends BaseComponent {
*
* @returns reference to checkbox placed in row localized by the name
*/
getCheckboxForElement = (item: string): Locator => this.getRowByName(item).locator('.mat-mdc-checkbox');
getCheckboxForElement = (item: string): Locator => this.getRowByName(item).locator('[type="checkbox"]');
getColumnHeaderByTitleLocator = (headerTitle: string): Locator => this.getChild('[role="columnheader"]', { hasText: headerTitle });
@@ -179,22 +169,6 @@ export class DataTableComponent extends BaseComponent {
await this.spinnerWaitForReload();
}
/**
* Click action from the expandable kebab menu for the element in datatable
*
* @param name title of the record you would like to proceed
* @param action provide button title for the action
* @param subAction if the action is in sub menu, then provide it here
*/
async performActionForElement(name: string, action: string, subAction?: string): Promise<void> {
await this.getActionsButtonByName(name).click();
await this.contextMenuActions.getButtonByText(action).click();
if (subAction) {
await this.contextMenuActions.getButtonByText(subAction).click();
}
}
/**
* This method is used when we want to perform double click on the dataTable row to open a file or folder
*
@@ -251,15 +225,17 @@ export class DataTableComponent extends BaseComponent {
if (!isSelected) {
const row = this.getRowByName(name);
await row.hover();
await row.locator(this.uncheckedCheckbox).click();
await row.locator(this.checkedCheckbox).waitFor({ state: 'visible', timeout: timeouts.large });
if (!(await this.getCheckboxForElement(name).isChecked())) {
await this.getCheckboxForElement(name).click();
}
}
}
}
async isRowSelected(itemName: string): Promise<boolean> {
const row = this.getRowByName(itemName);
return row.locator(this.checkedCheckbox).isVisible();
await row.hover();
return this.getCheckboxForElement(itemName).isChecked();
}
async getColumnHeaders(): Promise<Array<string>> {
@@ -26,7 +26,7 @@ import { expect, Page } from '@playwright/test';
import { BaseComponent } from '../base.component';
export class MatMenuComponent extends BaseComponent {
private static rootElement = '.mat-mdc-menu-content';
private static rootElement = '[role="menu"]';
constructor(page: Page) {
super(page, MatMenuComponent.rootElement);
@@ -60,11 +60,11 @@ export class MatMenuComponent extends BaseComponent {
}
async verifyActualMoreActions(expectedToolbarMore: string[]): Promise<void> {
await this.page.locator('.mat-mdc-menu-content').waitFor({ state: 'attached' });
const menus = await this.page.$$('.mat-mdc-menu-content .mat-mdc-menu-item');
await this.getChild('').waitFor();
const menus = await this.getChild('[role="menuitem"]').all();
const actualMoreActions: string[] = await Promise.all(
menus.map(async (button) => {
const title = await (await button.$('.mat-mdc-menu-item-text span')).innerText();
const title = await button.locator('span span').innerText();
return title || '';
})
);
@@ -26,18 +26,16 @@ import { Page } from '@playwright/test';
import { BaseComponent } from '../base.component';
export class DateTimePicker extends BaseComponent {
private static rootElement = '.mat-calendar';
calendar = this.getChild('.mat-datepicker-popup');
dayPicker = this.getChild('mat-month-view');
nextMonthBtn = this.getChild('.mat-calendar-next-button');
rootElemLocator = this.page.locator('.mat-datepicker-popup');
private static rootElement = '[role="dialog"]';
dayPicker = this.getChild('[role="grid"]');
nextMonthBtn = this.getChild('[aria-label="Next month"]');
constructor(page: Page) {
super(page, DateTimePicker.rootElement);
}
async isCalendarOpen(): Promise<boolean> {
const element = this.rootElemLocator;
const element = this.getChild('');
return element.isVisible();
}
@@ -29,7 +29,7 @@ export class AdfFolderDialogComponent extends BaseComponent {
private static rootElement = 'adf-folder-dialog';
public folderNameInputLocator = this.getChild('[id="adf-folder-name-input"]');
public folderNameInputHint = this.getChild('mat-hint');
public folderNameInputHint = this.getChild('[aria-atomic="true"] span');
public folderTitleInput = this.getChild('[id="adf-folder-title-input"]');
public folderDescriptionInput = this.getChild('[id="adf-folder-description-input"]');
public createButton = this.getChild('[id="adf-folder-create-button"]');
@@ -40,7 +40,7 @@ export class AdfFolderDialogComponent extends BaseComponent {
}
public getLabelText = (text: string) => this.getChild('label', { hasText: text });
public getRequiredMarker = (text: string) => this.getLabelText(text).locator('.mat-mdc-form-field-required-marker');
public getRequiredMarker = (text: string) => this.getLabelText(text).locator('span');
/**
* This method is used when we want to fill in Create new Folder Dialog and choose Create button
@@ -37,9 +37,9 @@ export class AdfLibraryDialogComponent extends BaseComponent {
}
public getLabelText = (text: string) => this.getChild('label', { hasText: text });
public getRequiredMarker = (text: string) => this.getLabelText(text).locator('.mat-mdc-form-field-required-marker');
public getDialogTitle = (text: string) => this.getChild('.mat-mdc-dialog-title', { hasText: text });
public getErrorByText = (text: string): Locator => this.page.locator('mat-error', { hasText: text });
public getRequiredMarker = (text: string) => this.getLabelText(text).locator('span');
public getDialogTitle = (text: string) => this.getChild('h2', { hasText: text });
public getErrorByText = (text: string): Locator => this.getChild('.adf-library-dialog-form-field').getByText(text);
/**
* This method is used when we want to fill in Create Library Dialog and choose Create button
@@ -27,23 +27,23 @@ import { BaseComponent } from '../base.component';
export class ContentNodeSelectorDialog extends BaseComponent {
private static rootElement = 'adf-content-node-selector';
constructor(page: Page) {
super(page, ContentNodeSelectorDialog.rootElement);
}
private selectedRow = this.getChild('.adf-is-selected');
private getRowByName = (name: string | number): Locator => this.getChild(`adf-datatable-row`, { hasText: name.toString() });
public cancelButton = this.getChild('[data-automation-id="content-node-selector-actions-cancel"]');
public actionButton = this.getChild('[data-automation-id="content-node-selector-actions-choose"]');
public locationDropDown = this.getChild('[id="site-dropdown-container"] mat-form-field');
private selectedRow = this.getChild('.adf-is-selected');
getOptionLocator = (optionName: string): Locator =>
this.page.locator('.mat-mdc-select-panel .mdc-list-item__primary-text', { hasText: optionName });
private getRowByName = (name: string | number): Locator => this.getChild(`adf-datatable-row`, { hasText: name.toString() });
public locationDropDown = this.getChild('[id="site-dropdown-container"] .adf-sites-dropdown-form-field');
getOptionLocator = (optionName: string): Locator => this.page.locator('[role=listbox] [role=option]', { hasText: optionName }).first();
getDialogTitle = (text: string) => this.getChild('[data-automation-id="content-node-selector-title"]', { hasText: text });
getBreadcrumb = (text: string) => this.getChild('[data-automation-id="current-folder"]', { hasText: text });
getFolderIcon = this.getChild('[data-automation-id="dropdown-breadcrumb-trigger"]');
loadMoreButton = this.getChild('[data-automation-id="adf-infinite-pagination-button"]');
constructor(page: Page) {
super(page, ContentNodeSelectorDialog.rootElement);
}
async loadMoreNodes(): Promise<void> {
await this.spinnerWaitForReload();
while (await this.loadMoreButton.isVisible()) {
@@ -27,7 +27,7 @@ import { BaseComponent } from '../base.component';
import { timeouts } from '../../../utils';
export class CreateFromTemplateDialogComponent extends BaseComponent {
private static rootElement = 'mat-dialog-container';
private static rootElement = '.aca-create-from-template-dialog';
constructor(page: Page) {
super(page, CreateFromTemplateDialogComponent.rootElement);
@@ -26,7 +26,7 @@ import { Page } from '@playwright/test';
import { BaseComponent } from '../base.component';
export class EditDialog extends BaseComponent {
private static rootElement = 'mat-dialog-container';
private static rootElement = 'adf-folder-dialog';
public editDialog = this.getChild('');
public titleInput = this.getChild('[data-automation-id="adf-folder-dialog-title"]');
@@ -34,7 +34,7 @@ export class EditDialog extends BaseComponent {
public updateButton = this.getChild('#adf-folder-create-button');
public descriptionInput = this.getChild('#adf-folder-description-input');
public nameInput = this.getChild('#adf-folder-name-input');
public fieldHint = this.getChild('mat-hint');
public fieldHint = this.getChild('[aria-atomic="true"] span');
constructor(page: Page) {
super(page, EditDialog.rootElement);
@@ -27,22 +27,21 @@ import { BaseComponent } from '../base.component';
export class LinkRulesDialog extends BaseComponent {
private static rootElement = 'aca-rule-set-picker';
constructor(page: Page) {
super(page, LinkRulesDialog.rootElement);
}
private getRowByName = (name: string | number): Locator => this.getChild(`adf-datatable-row`, { hasText: name.toString() });
cancelButton = this.getChild('[data-automation-id="content-node-selector-actions-cancel"]');
selectFolderButton = this.getChild('button', { hasText: ' Select folder ' });
emptyLinkRules = this.getChild('.adf-empty-content__title');
getOptionLocator = (optionName: string): Locator =>
this.page.locator('.mat-mdc-select-panel .mdc-list-item__primary-text', { hasText: optionName });
private getRowByName = (name: string | number): Locator => this.getChild(`adf-datatable-row`, { hasText: name.toString() });
getOptionLocator = (optionName: string): Locator => this.page.locator('[role=listbox] [role=option]', { hasText: optionName }).first();
getDialogTitle = (text: string) => this.getChild('[data-automation-id="content-node-selector-title"]', { hasText: text });
getBreadcrumb = (text: string) => this.getChild('[data-automation-id="current-folder"]', { hasText: text });
getFolderIcon = this.getChild('.adf-dropdown-breadcrumb-icon', { hasText: 'folder' });
getLibraryIcon = this.getChild('.adf-empty-content__icon', { hasText: 'library_books' });
constructor(page: Page) {
super(page, LinkRulesDialog.rootElement);
}
async selectDestination(folderName: string): Promise<void> {
const row = this.getRowByName(folderName);
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ElementHandle, Locator, Page } from '@playwright/test';
import { ElementHandle, Page } from '@playwright/test';
import { BaseComponent } from '../base.component';
import { timeouts } from '../../../utils';
import { DateTimePicker } from '../datetime-picker/datetime-picker.component';
@@ -43,12 +43,13 @@ export class ShareDialogComponent extends BaseComponent {
urlAction = this.getChild('.adf-input-action');
expireToggle = this.getChild(`[data-automation-id='adf-expire-toggle']`);
expireInput = this.getChild('input[formcontrolname="time"]');
datetimePickerButton = this.getChild('.mat-datepicker-toggle');
datetimePickerButton = this.getChild('[data-automation-id="adf-content-share-expiration-field"] button');
dateErrorText = this.getChild('[data-automation-id="adf-share-link-input-warning"]');
clockIcon = this.getChild('[adf-icon="timer"]');
dateTimePicker = new DateTimePicker(this.page);
getDialogLabel = () => this.getChild('label').innerText();
getErrorByText = (text: string): Locator => this.page.locator('mat-error', { hasText: text });
async getLabels(): Promise<Array<string>> {
return this.page.$$eval('.adf-share-link__label', (elements) => elements.map((element) => element.textContent));
@@ -29,8 +29,8 @@ export class ManageRules extends BaseComponent {
private static rootElement = '.aca-manage-rules';
public getGroupsList = (optionName: string): Locator => this.getChild('.aca-rule-list-item__header', { hasText: optionName });
public ruleToggle = this.getChild('.aca-manage-rules__container .mdc-switch').first();
public ruleToggleFalse = this.getChild('aca-rule-list-grouping aca-rule-list-item .mdc-switch--unselected').first();
public ruleToggle = this.getChild('.aca-manage-rules__container [role="switch"]').first();
public ruleToggleFalse = this.getChild('aca-rule-list-grouping aca-rule-list-item [role="switch"][aria-checked="false"]').first();
public ruleDetailsTitle = this.getChild('.aca-manage-rules__container__rule-details__header__title__name');
public ruleDetailsDeleteButton = this.getChild('#delete-rule-btn');
public ruleDetailsEditButton = this.getChild('#edit-rule-btn');
@@ -68,8 +68,8 @@ export class PaginationComponent extends BaseComponent {
async spinnerWaitForReload(): Promise<void> {
try {
await this.page.locator('mat-progress-spinner').waitFor({ state: 'attached', timeout: 2000 });
await this.page.locator('mat-progress-spinner').waitFor({ state: 'detached', timeout: 2000 });
await this.page.locator('[role="progressbar"]').waitFor({ state: 'attached', timeout: 2000 });
await this.page.locator('[role="progressbar"]').waitFor({ state: 'detached', timeout: 2000 });
} catch (e) {
this.logger.info('Spinner was not present');
}
@@ -44,5 +44,5 @@ export class SearchFilters extends BaseComponent {
public menuCardClose = this.page.locator('.adf-search-filter-title-action');
public menuCardClear = this.page.locator('#cancel-filter-button');
public menuCardApply = this.page.locator('#apply-filter-button');
public dropdownOptions = this.page.locator(`mat-option`);
public dropdownOptions = this.page.locator(`[role="option"]`);
}
@@ -37,13 +37,13 @@ export class SearchFiltersProperties extends BaseComponent {
public fileSizeInput = this.getChild(`[placeholder$='Enter file size']`);
public unitButton = this.getChild(`[data-automation-id='adf-search-properties-file-size-unit']`);
public fileTypeInput = this.getChild(`[placeholder$='File Type']`);
public atLeastOption = this.page.locator(`mat-option`, { hasText: 'At Least' });
public atMostOption = this.page.locator(`mat-option`, { hasText: 'At Most' });
public exactlyOption = this.page.locator(`mat-option`, { hasText: 'Exactly' });
public kbUnit = this.page.locator(`mat-option`, { hasText: 'KB' });
public mbUnit = this.page.locator(`mat-option`, { hasText: 'MB' });
public gbUnit = this.page.locator(`mat-option`, { hasText: 'GB' });
public dropdownOptions = this.page.locator(`mat-option`);
public atLeastOption = this.page.locator(`[role="option"]`, { hasText: 'At Least' });
public atMostOption = this.page.locator(`[role="option"]`, { hasText: 'At Most' });
public exactlyOption = this.page.locator(`[role="option"]`, { hasText: 'Exactly' });
public kbUnit = this.page.locator(`[role="option"]`, { hasText: 'KB' });
public mbUnit = this.page.locator(`[role="option"]`, { hasText: 'MB' });
public gbUnit = this.page.locator(`[role="option"]`, { hasText: 'GB' });
public dropdownOptions = this.page.locator(`[role="option"]`);
async setPropertiesParameters(
page: SearchPage,
@@ -41,18 +41,15 @@ export class SearchOverlayComponent extends BaseComponent {
}
async isFoldersOptionChecked() {
const optClass = await this.searchFoldersOption.getAttribute('class');
return optClass.includes('.mat-mdc-checkbox-checked');
return this.searchFoldersOption.isChecked();
}
async isFilesOptionChecked() {
const optClass = await this.searchFilesOption.getAttribute('class');
return optClass.includes('.mat-mdc-checkbox-checked');
return this.searchFilesOption.isChecked();
}
async isLibrariesOptionChecked() {
const optClass = await this.searchLibrariesOption.getAttribute('class');
return optClass.includes('.mat-mdc-checkbox-checked');
return this.searchLibrariesOption.isChecked();
}
async clearOptions() {
@@ -33,8 +33,8 @@ export class SearchSortingPicker extends BaseComponent {
public actionMenu = this.page.locator('[data-automation-id="auto_header_content_id_$thumbnail"]');
public sortOrderButton = this.page.locator('#aca-button-sorting-menu');
public sortByDropdownExpanded = this.page.locator('.mat-mdc-menu-panel').first();
public sortByList = this.page.locator('.mat-mdc-menu-panel button');
public sortByDropdownExpanded = this.page.locator('[role="menu"]').first();
public sortByList = this.page.locator('[role="menu"] button');
constructor(page: Page, rootElement = SearchSortingPicker.rootElement) {
super(page, rootElement);
@@ -26,7 +26,7 @@ import { Page } from '@playwright/test';
import { BaseComponent } from './base.component';
export class SpinnerComponent extends BaseComponent {
private static rootElement = 'mat-progress-spinner';
private static rootElement = '[role="progressbar"]';
constructor(page: Page, rootElement = SpinnerComponent.rootElement) {
super(page, rootElement);
@@ -50,7 +50,9 @@ export const getExcludedTestsRegExpArray = (excludedJson: any, projectName: stri
if (testIdsToExclude.length > 0) {
console.warn(
`${prefix} ❌ Tests excluded for browser '${currentBrowser}' because of 🐛 : ${testIdsToExclude.join(', ')} (from keys: ${relevantKeys.join(', ')})`
`${prefix} ❌ Tests excluded for browser '${currentBrowser}' because of 🐛 : ${testIdsToExclude.join(', ')} (from keys: ${relevantKeys.join(
', '
)})`
);
} else {
console.info(`${prefix} ✅ No excluded tests for browser '${currentBrowser}' 🎉`);