diff --git a/.eslintrc.js b/.eslintrc.js index 4f9f6f11bf8e2..c366864411d93 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1439,6 +1439,54 @@ module.exports = { 'mocha/no-skipped-tests': 'off', }, }, + { + files: [ + 'packages/kbn-scout/src/playwright/**/*.ts', + 'x-pack/solutions/observability/packages/kbn-scout-oblt/src/playwright/**/*.ts', + 'src/platform/plugins/**/ui_tests/**/*.ts', + 'x-pack/platform/plugins/**/ui_tests/**/*.ts', + 'x-pack/solutions/**/plugins/**/ui_tests/**/*.ts', + ], + excludedFiles: ['packages/kbn-scout/src/playwright/**/*.test.ts'], + extends: ['plugin:playwright/recommended'], + plugins: ['playwright'], + settings: { + playwright: { + globalAliases: { + test: ['test', 'spaceTest'], + }, + }, + }, + rules: { + 'playwright/no-commented-out-tests': 'error', + 'playwright/no-conditional-expect': 'error', + 'playwright/no-conditional-in-test': 'warn', + 'playwright/no-duplicate-hooks': 'error', + 'playwright/no-focused-test': 'error', + 'playwright/no-get-by-title': 'error', + 'playwright/no-nth-methods': 'error', + 'playwright/no-page-pause': 'error', + 'playwright/no-restricted-matchers': 'error', + 'playwright/no-slowed-test': 'error', + 'playwright/no-standalone-expect': 'error', + 'playwright/no-unsafe-references': 'error', + 'playwright/no-wait-for-selector': 'warn', + 'playwright/max-nested-describe': ['error', { max: 1 }], + 'playwright/missing-playwright-await': 'error', + 'playwright/prefer-comparison-matcher': 'error', + 'playwright/prefer-equality-matcher': 'error', + 'playwright/prefer-hooks-in-order': 'error', + 'playwright/prefer-hooks-on-top': 'error', + 'playwright/prefer-strict-equal': 'error', + 'playwright/prefer-to-be': 'error', + 'playwright/prefer-to-contain': 'error', + 'playwright/prefer-to-have-count': 'error', + 'playwright/prefer-to-have-length': 'error', + 'playwright/prefer-web-first-assertions': 'error', + 'playwright/require-to-throw-message': 'error', + 'playwright/require-top-level-describe': 'error', + }, + }, { files: ['x-pack/solutions/security/plugins/lists/public/**/!(*.test).{js,mjs,ts,tsx}'], plugins: ['react-perf'], diff --git a/package.json b/package.json index 1896f9ec750a2..f319e0c267511 100644 --- a/package.json +++ b/package.json @@ -1746,6 +1746,7 @@ "eslint-plugin-mocha": "^10.1.0", "eslint-plugin-no-unsanitized": "^4.0.2", "eslint-plugin-node": "^11.1.0", + "eslint-plugin-playwright": "^2.2.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", diff --git a/packages/kbn-scout/src/playwright/fixtures/test/scout_page/single_thread.ts b/packages/kbn-scout/src/playwright/fixtures/test/scout_page/single_thread.ts index 12a50ee925815..0ff8af76a37dc 100644 --- a/packages/kbn-scout/src/playwright/fixtures/test/scout_page/single_thread.ts +++ b/packages/kbn-scout/src/playwright/fixtures/test/scout_page/single_thread.ts @@ -59,6 +59,8 @@ function extendPageWithTestSubject(page: Page): ScoutPage['testSubj'] { await page.locator(testSubjSelector).click(); for (const char of text) { await page.keyboard.insertText(char); + // it is important to delay characters input to avoid flakiness, default is 25 ms + // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(delay); } }; diff --git a/renovate.json b/renovate.json index 51eb24b643ef1..7f7b15d764d82 100644 --- a/renovate.json +++ b/renovate.json @@ -1429,6 +1429,7 @@ "eslint-plugin-mocha", "eslint-plugin-no-unsanitized", "eslint-plugin-node", + "eslint-plugin-playwright", "eslint-plugin-react", "eslint-plugin-react-hooks", "eslint-plugin-react-perf", diff --git a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/error_handling.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/error_handling.spec.ts index 97c4e18f7f2dc..81dc8ffad925c 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/error_handling.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/error_handling.spec.ts @@ -18,15 +18,15 @@ spaceTest.describe('Discover app - errors', { tag: tags.ESS_ONLY }, () => { }); }); - spaceTest.afterAll(async ({ scoutSpace }) => { - await scoutSpace.savedObjects.cleanStandardList(); - }); - spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { await browserAuth.loginAsViewer(); await pageObjects.discover.goto(); }); + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + }); + spaceTest('should render invalid scripted field error', async ({ page }) => { await page.testSubj.locator('discoverErrorCalloutTitle').waitFor({ state: 'visible' }); await expect( diff --git a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/saved_searches.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/saved_searches.spec.ts index 24130c6ec5e5d..83368b86e466f 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/saved_searches.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/saved_searches.spec.ts @@ -54,15 +54,15 @@ spaceTest.describe( await scoutSpace.uiSettings.setDefaultTime({ from: START_TIME, to: END_TIME }); }); + spaceTest.beforeEach(async ({ browserAuth }) => { + await browserAuth.loginAsPrivilegedUser(); + }); + spaceTest.afterAll(async ({ scoutSpace }) => { await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); await scoutSpace.savedObjects.cleanStandardList(); }); - spaceTest.beforeEach(async ({ browserAuth }) => { - await browserAuth.loginAsPrivilegedUser(); - }); - spaceTest('should customize time range on dashboards', async ({ pageObjects, page }) => { await pageObjects.dashboard.goto(); await pageObjects.dashboard.openNewDashboard(); diff --git a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions.spec.ts index 64fe9869f3dc0..69e5aadd8292a 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions.spec.ts @@ -21,16 +21,16 @@ spaceTest.describe( }); }); - spaceTest.afterAll(async ({ scoutSpace }) => { - await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); - await scoutSpace.savedObjects.cleanStandardList(); - }); - spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { await browserAuth.loginAsViewer(); await pageObjects.discover.goto(); }); + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + spaceTest('dont show up if outside of range', async ({ page, pageObjects }) => { await pageObjects.datePicker.setAbsoluteRange(testData.LOGSTASH_OUT_OF_RANGE_DATES); await page.testSubj.fill('queryInput', 'extension.raw : '); diff --git a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_non_time_based.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_non_time_based.spec.ts index 6bed83254f003..b4b6e76dbe8ac 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_non_time_based.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_non_time_based.spec.ts @@ -19,16 +19,16 @@ spaceTest.describe( await scoutSpace.uiSettings.setDefaultIndex(testData.DATA_VIEW_NAME.NO_TIME_FIELD); }); - spaceTest.afterAll(async ({ scoutSpace }) => { - await scoutSpace.uiSettings.unset('defaultIndex'); - await scoutSpace.savedObjects.cleanStandardList(); - }); - spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { await browserAuth.loginAsViewer(); await pageObjects.discover.goto(); }); + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + spaceTest( 'shows all auto-suggest options for a filter in discover context app', async ({ page }) => { diff --git a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_use_time_range_disabled.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_use_time_range_disabled.spec.ts index db90b22553bff..b31851ce395e3 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_use_time_range_disabled.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel_tests/value_suggestions_use_time_range_disabled.spec.ts @@ -22,17 +22,17 @@ spaceTest.describe( await scoutSpace.uiSettings.set({ 'autocomplete:useTimeRange': false }); }); + spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { + await browserAuth.loginAsViewer(); + await pageObjects.discover.goto(); + }); + spaceTest.afterAll(async ({ scoutSpace }) => { await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); await scoutSpace.uiSettings.set({ 'autocomplete:useTimeRange': true }); await scoutSpace.savedObjects.cleanStandardList(); }); - spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { - await browserAuth.loginAsViewer(); - await pageObjects.discover.goto(); - }); - spaceTest('show up if outside of range', async ({ page, pageObjects }) => { await pageObjects.datePicker.setAbsoluteRange(testData.LOGSTASH_OUT_OF_RANGE_DATES); await page.testSubj.fill('queryInput', 'extension.raw : '); diff --git a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/saved_search_embeddable.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/saved_search_embeddable.spec.ts index 6c37611dbc202..e6a77e970809a 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/saved_search_embeddable.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/saved_search_embeddable.spec.ts @@ -40,6 +40,7 @@ const createSavedSearch = async ( test.describe('Discover app - saved search embeddable', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { const SAVED_SEARCH_TITLE = 'TempSearch'; const SAVED_SEARCH_ID = '90943e30-9a47-11e8-b64d-95841ca0b247'; + test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => { await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.LOGSTASH); await kbnClient.importExport.load(testData.KBN_ARCHIVES.DASHBOARD_DRILLDOWNS); @@ -49,16 +50,16 @@ test.describe('Discover app - saved search embeddable', { tag: tags.DEPLOYMENT_A }); }); - test.afterAll(async ({ kbnClient, uiSettings }) => { - await uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); - await kbnClient.savedObjects.cleanStandardList(); - }); - test.beforeEach(async ({ browserAuth, pageObjects }) => { await browserAuth.loginAsPrivilegedUser(); await pageObjects.dashboard.goto(); }); + test.afterAll(async ({ kbnClient, uiSettings }) => { + await uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); + await kbnClient.savedObjects.cleanStandardList(); + }); + test('should allow removing the dashboard panel after the underlying saved search has been deleted', async ({ kbnClient, page, diff --git a/x-pack/platform/plugins/shared/maps/ui_tests/tests/full_screen_mode.spec.ts b/x-pack/platform/plugins/shared/maps/ui_tests/tests/full_screen_mode.spec.ts index 861baafc7e8c3..e6f4d89ef1db1 100644 --- a/x-pack/platform/plugins/shared/maps/ui_tests/tests/full_screen_mode.spec.ts +++ b/x-pack/platform/plugins/shared/maps/ui_tests/tests/full_screen_mode.spec.ts @@ -33,16 +33,16 @@ test.describe( const baseMapBtn = page.getByRole('button', { name: 'Basemap' }); await expect(fullScreenBtn).toBeVisible(); - await expect(exitFullScreenBtn).not.toBeVisible(); + await expect(exitFullScreenBtn).toBeHidden(); await expect(visibleChrome).toBeVisible(); - await expect(hiddenChrome).not.toBeVisible(); + await expect(hiddenChrome).toBeHidden(); await expect(baseMapBtn).toBeVisible(); await fullScreenBtn.click(); - await expect(fullScreenBtn).not.toBeVisible(); + await expect(fullScreenBtn).toBeHidden(); await expect(exitFullScreenBtn).toBeVisible(); - await expect(visibleChrome).not.toBeVisible(); + await expect(visibleChrome).toBeHidden(); await expect(hiddenChrome).toBeVisible(); await expect(baseMapBtn).toBeVisible(); diff --git a/x-pack/solutions/observability/packages/kbn-scout-oblt/src/playwright/page_objects/custom_logs.ts b/x-pack/solutions/observability/packages/kbn-scout-oblt/src/playwright/page_objects/custom_logs.ts index 6001bf086fed3..825d8475ce64a 100644 --- a/x-pack/solutions/observability/packages/kbn-scout-oblt/src/playwright/page_objects/custom_logs.ts +++ b/x-pack/solutions/observability/packages/kbn-scout-oblt/src/playwright/page_objects/custom_logs.ts @@ -104,8 +104,7 @@ export class CustomLogsPage { async clickAdvancedSettingsButton() { return this.page.testSubj .locator('obltOnboardingCustomLogsAdvancedSettings') - .getByRole('button') - .first() + .locator('button.euiAccordion__button') .click(); } diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/add_custom_integration.spec.ts b/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/add_custom_integration.spec.ts index ad22d72cbe34b..e04abdec5e5cf 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/add_custom_integration.spec.ts +++ b/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/add_custom_integration.spec.ts @@ -56,7 +56,7 @@ test.describe( await customLogs.selectPlatform('windows'); await expect(customLogs.autoDownloadConfigurationToggle).toBeDisabled(); await expect(customLogs.windowsInstallElasticAgentDocLink).toBeVisible(); - await expect(customLogs.installCodeSnippet).not.toBeVisible(); + await expect(customLogs.installCodeSnippet).toBeHidden(); await expect( customLogs.configureElasticAgentStep.getByText('Step 2 is disabled') ).toBeVisible(); diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/configuration.spec.ts b/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/configuration.spec.ts index c3cd104b461a8..d3bbff71d7eca 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/configuration.spec.ts +++ b/x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_tests/custom_logs/configuration.spec.ts @@ -47,7 +47,7 @@ test.describe( test(`should allow updating Advanced Settings`, async ({ pageObjects: { customLogs } }) => { await customLogs.getLogFilePathInputField(0).fill(logsFilePath); - await expect(customLogs.advancedSettingsContent).not.toBeVisible(); + await expect(customLogs.advancedSettingsContent).toBeHidden(); await customLogs.clickAdvancedSettingsButton(); await expect( customLogs.advancedSettingsContent, @@ -63,13 +63,13 @@ test.describe( await customLogs.namespaceInput.fill('default'); await expect(customLogs.customConfigInput).toHaveValue(''); - await expect(customLogs.continueButton).not.toBeDisabled(); + await expect(customLogs.continueButton).toBeEnabled(); await customLogs.clickAdvancedSettingsButton(); await expect( customLogs.advancedSettingsContent, 'Advanced Settings should be closed' - ).not.toBeVisible(); + ).toBeHidden(); }); test('should validate Integration Name field', async ({ diff --git a/yarn.lock b/yarn.lock index 1a3b866b2af6a..dd4816660df39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18616,6 +18616,13 @@ eslint-plugin-node@^11.1.0: resolve "^1.10.1" semver "^6.1.0" +eslint-plugin-playwright@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz#d7eda21e670274fc0c006e11ba5cc2c8417b2a6e" + integrity sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg== + dependencies: + globals "^13.23.0" + eslint-plugin-prettier@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" @@ -20277,10 +20284,10 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.19.0, globals@^13.20.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== +globals@^13.19.0, globals@^13.20.0, globals@^13.23.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2"