-
Notifications
You must be signed in to change notification settings - Fork 4
PMM-14365 New native PMM Navigation #1028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
ab764fb
62f2871
eb7b68c
0480a06
f29d3df
162b80b
0811e6f
d129abd
9ea793f
59db7a5
14eb7eb
a8615ec
a6d203d
b087547
12da1f2
829ba68
b99e79a
35eaa70
6ff18c0
fe782cc
84fdcf6
a962904
bf69a3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ Before(async ({ I }) => { | |
| Scenario( | ||
| 'PMM-T2050 - Verify PostgreSQL Instance Summary Dashboard @nightly @dashboards', | ||
| async ({ I, dashboardPage }) => { | ||
| const { service_name } = await inventoryAPI.getServiceDetailsByStartsWithName('pdpgsql_pmm_'); | ||
| const { service_name } = await inventoryAPI.apiGetNodeInfoByServiceName(SERVICE_TYPE.POSTGRESQL, 'pdpgsql_pmm_', 'patroni'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this change?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With changes made to patroni the service chosen for checking the dashboard was being one of the patronis, and it had no data, so I changed it to exclude patroni and instead pickup the main service (I noticed the existence of another method that had this functionality so I just reused) This change isn't related to new native, it was just a test bug that I fixed here, I can remove |
||
| const url = I.buildUrlWithParams(dashboardPage.postgresqlInstanceSummaryDashboard.url, { service_name, from: 'now-1h' }); | ||
|
|
||
| I.amOnPage(url); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| /* eslint-disable func-names */ | ||
| /* eslint-disable no-underscore-dangle */ | ||
| const { event, container } = require('codeceptjs'); | ||
|
|
||
| function getSelector(locator) { | ||
| let selector = locator; | ||
|
|
||
| if (!locator) return null; | ||
|
|
||
| if (typeof locator === 'object') { | ||
| if (locator.xpath) { | ||
| selector = `xpath=${locator.xpath}`; | ||
| } else if (locator.css) { | ||
| selector = locator.css; | ||
| } else if (locator.value && locator.type) { | ||
| if (locator.type === 'xpath') { | ||
| selector = `xpath=${locator.value}`; | ||
| } else if (locator.type === 'css') { | ||
| selector = locator.value; | ||
| } else { | ||
| selector = locator.value; | ||
| } | ||
| } else { | ||
| selector = locator.toString(); | ||
| } | ||
| } | ||
|
|
||
| if (typeof selector === 'string') { | ||
| if (selector.startsWith('{xpath:') && selector.endsWith('}')) { | ||
| return `xpath=${selector.substring(7, selector.length - 1).trim()}`; | ||
| } | ||
|
|
||
| if (selector.startsWith('{css:') && selector.endsWith('}')) { | ||
| return selector.substring(5, selector.length - 1).trim(); | ||
| } | ||
|
|
||
| if (selector.startsWith('$')) { | ||
| return `[data-testid="${selector.substring(1)}"]`; | ||
| } | ||
| } | ||
|
|
||
| return selector; | ||
| } | ||
|
|
||
| async function switchToGrafana(helper) { | ||
| const grafanaIframe = '#grafana-iframe'; | ||
|
|
||
| await helper.switchTo(); | ||
| if (helper.page) await helper.page.waitForLoadState('domcontentloaded'); | ||
|
|
||
| await helper.waitForVisible(grafanaIframe, 60); | ||
| await helper.switchTo(grafanaIframe); | ||
|
|
||
| if (helper.page) helper.context = helper.page.frameLocator(grafanaIframe); | ||
| } | ||
|
|
||
| async function resetContext(helper) { | ||
| if (helper.browserContext) { | ||
| const pages = helper.browserContext.pages(); | ||
|
|
||
| if (pages.length > 0) [helper.page] = pages; | ||
| } | ||
|
|
||
| await helper.switchTo(); | ||
| helper.context = null; | ||
| } | ||
|
|
||
| function applyOverride(helper, methodName, wrapperFunction) { | ||
| const originalMethod = helper[methodName]; | ||
|
|
||
| helper[methodName] = async function pmmMethodWrapper(...args) { | ||
| return wrapperFunction.apply(this, [originalMethod, ...args]); | ||
| }; | ||
| } | ||
|
|
||
| function applyContextOverride(helper, methodName, contextAction) { | ||
| applyOverride(helper, methodName, async function (original, ...args) { | ||
| if (helper.context) return contextAction.apply(this, args); | ||
|
|
||
| return original.apply(this, args); | ||
| }); | ||
| } | ||
|
|
||
| module.exports = function pmmGrafanaIframeHook() { | ||
| const helper = container.helpers('Playwright'); | ||
| const navigationMethods = ['amOnPage', 'refreshPage', 'openNewTab', 'switchToNextTab', 'switchToPreviousTab']; | ||
| const noIframeMethods = ['openNewTab']; | ||
| const noIframeUrls = ['login', 'help', 'updates']; | ||
|
|
||
| navigationMethods.forEach((methodName) => { | ||
| applyOverride(helper, methodName, async function (original, ...args) { | ||
| await resetContext(helper); | ||
| await original.apply(this, args); | ||
|
|
||
| if (methodName === 'amOnPage' && noIframeUrls.some((url) => args[0].includes(url))) return; | ||
|
|
||
| if (noIframeMethods.includes(methodName)) return; | ||
|
|
||
| await switchToGrafana(helper); | ||
| }); | ||
| }); | ||
| applyOverride(helper, 'pressKey', async function (original, key) { | ||
| function getPage() { | ||
| if (helper.page && helper.page.keyboard) return helper.page; | ||
|
|
||
| if (helper.browserContext) { | ||
| const pages = helper.browserContext.pages(); | ||
|
|
||
| if (pages.length > 0) { | ||
| const [firstPage] = pages; | ||
|
|
||
| return firstPage; | ||
| } | ||
| } | ||
|
|
||
| return helper.page; | ||
| } | ||
|
|
||
| const page = getPage(); | ||
|
|
||
| if (helper.context && page && page.keyboard) { | ||
| const modifiers = ['Control', 'Command', 'Alt', 'Shift', 'Meta']; | ||
|
|
||
| if (Array.isArray(key) && key.length === 2 && modifiers.includes(key[0])) { | ||
| await page.keyboard.down(key[0]); | ||
| await page.keyboard.press(key[1]); | ||
| await page.keyboard.up(key[0]); | ||
| } else if (Array.isArray(key)) { | ||
| for (const keyItem of key) { | ||
| await helper.pressKey(keyItem); | ||
| } | ||
| } else { | ||
| await page.keyboard.press(key); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| await original.call(this, key); | ||
| }); | ||
| applyContextOverride(helper, 'grabTextFrom', async (locator) => helper.context.locator(getSelector(locator)).first().textContent()); | ||
| applyContextOverride(helper, 'grabTextFromAll', async (locator) => helper.context.locator(getSelector(locator)).allTextContents()); | ||
| applyContextOverride(helper, 'waitForText', async (text, seconds = null, context = null) => { | ||
| await helper.context.locator(getSelector(context) || 'body').filter({ hasText: text }).first().waitFor({ | ||
| state: 'visible', | ||
| timeout: seconds ? seconds * 1000 : helper.options.waitForTimeout, | ||
| }); | ||
| }); | ||
| applyContextOverride(helper, 'waitForDetached', async (locator, seconds = null) => { | ||
| await helper.context | ||
| .locator(getSelector(locator)) | ||
| .first() | ||
| .waitFor({ state: 'detached', timeout: seconds ? seconds * 1000 : helper.options.waitForTimeout }); | ||
| }); | ||
| applyContextOverride(helper, 'waitForValue', async (field, value, seconds = null) => { | ||
| const waitTimeout = seconds ? seconds * 1000 : helper.options.waitForTimeout; | ||
| const locator = helper.context.locator(getSelector(field)).first(); | ||
| const startTime = Date.now(); | ||
|
|
||
| while (Date.now() < startTime + waitTimeout) { | ||
| const inputValue = await locator.inputValue().catch(() => ''); | ||
|
|
||
| if (inputValue.includes(value)) return; | ||
|
|
||
| await new Promise((resolve) => { setTimeout(resolve, 100); }); | ||
| } | ||
| throw new Error(`Wait for value "${value}" failed for field ${field}`); | ||
| }); | ||
| applyContextOverride(helper, 'moveCursorTo', async (locator, offsetX = 0, offsetY = 0) => { | ||
| const element = helper.context.locator(getSelector(locator)).first(); | ||
|
|
||
| await element.evaluate((elementInstance) => { | ||
| elementInstance.scrollIntoView({ block: 'center', inline: 'center' }); | ||
| }); | ||
| await element.hover({ position: { x: offsetX, y: offsetY }, force: true }); | ||
| }); | ||
| applyOverride(helper, 'usePlaywrightTo', async function (original, description, callback) { | ||
| return original.call(this, description, async (args) => { | ||
| if (helper.context) { | ||
| args.page = helper.context; | ||
|
|
||
| if (!args.page.evaluate) { | ||
| Object.defineProperty(args.page, 'evaluate', { | ||
| async value(functionToExecute, argument) { | ||
| return args.page.locator('body').evaluate(functionToExecute, argument); | ||
| }, | ||
| writable: true, | ||
| configurable: true, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return callback(args); | ||
| }); | ||
| }); | ||
|
|
||
| event.dispatcher.on(event.test.before, resetContext.bind(null, helper)); | ||
| event.dispatcher.on(event.test.after, resetContext.bind(null, helper)); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,7 +50,7 @@ class QueryAnalyticsFilters { | |
|
|
||
| await locator.waitFor({ state: 'attached' }); | ||
| await locator.type(filterName); | ||
| await page.waitForTimeout(200); | ||
| await new Promise((resolve) => { setTimeout(resolve, 200); }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need this change?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because before the test runs on the hook we change page to be handled as a frameLocator, so waitForTimeout doesn't exist in it, it was the only occurrence I could find on our tests, so I just changed the waitForTimeout to a nodejs sleep instead. |
||
| }); | ||
| } | ||
|
|
||
|
|
@@ -111,15 +111,9 @@ class QueryAnalyticsFilters { | |
|
|
||
| selectContainFilter(filterName) { | ||
| I.waitForVisible(this.fields.groupHeaders, 30); | ||
| I.click(this.fields.groupHeaders); | ||
| I.fillField(this.fields.filterBy, filterName); | ||
| I.waitForVisible(this.fields.filterByName(filterName)); | ||
| I.usePlaywrightTo('Select QAN Filter', async ({ page }) => { | ||
| const locator = await page.locator(this.fields.filterByName(filterName).value); | ||
|
|
||
| await locator.first().waitFor({ state: 'attached' }); | ||
| await locator.first().click(); | ||
| }); | ||
| I.click(this.fields.filterByName(filterName)); | ||
| queryAnalyticsPage.waitForLoaded(); | ||
| I.click(this.fields.filterBy); | ||
| adminPage.customClearField(this.fields.filterBy); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.