mirror of
https://github.com/OliveTin/OliveTin
synced 2026-09-25 11:15:38 +00:00
309 lines
8.8 KiB
JavaScript
309 lines
8.8 KiB
JavaScript
import { By } from 'selenium-webdriver'
|
|
import fs from 'fs'
|
|
import { expect } from 'chai'
|
|
import { Condition } from 'selenium-webdriver'
|
|
|
|
export const DEFAULT_UI_WAIT_MS = 3000
|
|
const FAST_POLL_MS = 50
|
|
|
|
// Keep Selenium helpers in lockstep with the frontend DOM id helpers.
|
|
export {
|
|
ARGUMENT_FIELD_ID_PREFIX,
|
|
argumentFieldChoicesId,
|
|
argumentFieldId,
|
|
argumentFieldValidationElementId,
|
|
argumentFieldValueId
|
|
} from '../../frontend/resources/vue/utils/argumentFieldIds.js'
|
|
|
|
const executionDialogStatusBy = By.css('.execution-dialog-status')
|
|
const sidebarIds = ['mainnav', 'picocrank-sidebar']
|
|
|
|
const sidebarNavLinksSelector = [
|
|
sidebarCss('menu.navigation-links > li:not(.nav-section)'),
|
|
sidebarCss('menu.nav-section-links > li')
|
|
].join(', ')
|
|
|
|
let loadedPageGeneration = null
|
|
|
|
function sidebarCss (suffix) {
|
|
return sidebarIds.map((id) => `#${id} ${suffix}`).join(', ')
|
|
}
|
|
|
|
function isSamePagePath (currentUrl, targetUrl) {
|
|
try {
|
|
return new URL(currentUrl).pathname === new URL(targetUrl).pathname
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function waitUntil (description, fn, timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await webdriver.wait(
|
|
new Condition(description, fn),
|
|
timeoutMs,
|
|
undefined,
|
|
FAST_POLL_MS
|
|
)
|
|
}
|
|
|
|
async function getBodyAttribute (name) {
|
|
return webdriver.executeScript(
|
|
(attributeName) => document.body.getAttribute(attributeName),
|
|
name
|
|
)
|
|
}
|
|
|
|
async function isSidebarVisibleInBrowser () {
|
|
return webdriver.executeScript((ids) => {
|
|
for (const id of ids) {
|
|
const sidebar = document.getElementById(id)
|
|
if (!sidebar) {
|
|
continue
|
|
}
|
|
|
|
const classes = sidebar.className
|
|
if (classes.includes('shown') || classes.includes('stuck')) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}, sidebarIds)
|
|
}
|
|
|
|
async function countNavigationLinksInBrowser () {
|
|
return webdriver.executeScript(
|
|
(selector) => document.querySelectorAll(selector).length,
|
|
sidebarNavLinksSelector
|
|
)
|
|
}
|
|
|
|
export async function getActionButtons () {
|
|
// Currently, only the active dashboard's contents are rendered,
|
|
// so we don't need to scope the selector by dashboard title.
|
|
return await webdriver.findElements(By.css('.action-button button'))
|
|
}
|
|
|
|
export async function getExecutionDialogOutput() {
|
|
await waitUntil('Dialog with long int is visible', async () => {
|
|
const dialog = await webdriver.findElement({ id: 'execution-results-popup' })
|
|
return await dialog.isDisplayed()
|
|
})
|
|
|
|
const ret = await webdriver.executeScript('return window.logEntries.get(window.executionDialog.executionTrackingId).output')
|
|
|
|
return ret
|
|
}
|
|
|
|
export async function closeExecutionDialog() {
|
|
const btnClose = await webdriver.findElements(By.css('[title="Close"]'))
|
|
await btnClose[0].click()
|
|
}
|
|
|
|
export function takeScreenshotOnFailure (test, webdriver) {
|
|
if (test.state === 'failed') {
|
|
const title = test.fullTitle();
|
|
|
|
console.log(`Test failed, taking screenshot: ${title}`);
|
|
takeScreenshot(webdriver, title);
|
|
}
|
|
}
|
|
|
|
export function takeScreenshot (webdriver, title) {
|
|
return webdriver.takeScreenshot().then((img) => {
|
|
fs.mkdirSync('screenshots', { recursive: true });
|
|
|
|
title = title.replaceAll('config: ', '')
|
|
title = title.replaceAll(/[()|*<>:]/g, '_')
|
|
title = title + '.failed-test'
|
|
|
|
fs.writeFileSync('screenshots/' + title + '.png', img, 'base64')
|
|
})
|
|
}
|
|
|
|
export async function waitForDashboardLoaded(timeoutMs = DEFAULT_UI_WAIT_MS, expectedTitle = null) {
|
|
await waitUntil('wait for loaded-dashboard', async function () {
|
|
const attr = await getBodyAttribute('loaded-dashboard')
|
|
|
|
if (attr == null || attr === '') {
|
|
return false
|
|
}
|
|
|
|
if (expectedTitle != null) {
|
|
return attr === expectedTitle
|
|
}
|
|
|
|
return true
|
|
}, timeoutMs)
|
|
}
|
|
|
|
export async function waitForLogsPage(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await waitUntil('wait for logs page', async () => {
|
|
const url = await webdriver.getCurrentUrl()
|
|
return url.includes('/logs/') && !url.endsWith('/logs')
|
|
}, timeoutMs)
|
|
}
|
|
|
|
export async function waitForArgumentFormPage(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await waitUntil('wait for argument form page', async () => {
|
|
const url = await webdriver.getCurrentUrl()
|
|
return url.includes('/actionBinding/') && url.includes('/argumentForm')
|
|
}, timeoutMs)
|
|
}
|
|
|
|
export async function waitForArgumentFormReady(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await waitUntil('wait for argument form ready', async () => {
|
|
const attr = await getBodyAttribute('loaded-argument-form')
|
|
return attr != null && attr !== ''
|
|
}, timeoutMs)
|
|
}
|
|
|
|
export async function waitForExecutionComplete(timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await waitUntil('wait for execution to finish', async () => {
|
|
const statusElements = await webdriver.findElements(executionDialogStatusBy)
|
|
if (statusElements.length === 0) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
const statusText = await statusElements[0].getText()
|
|
return !statusText.includes('Still running') && !statusText.includes('Queued')
|
|
} catch {
|
|
return false
|
|
}
|
|
}, timeoutMs)
|
|
}
|
|
|
|
export async function getRootAndWait() {
|
|
const targetUrl = runner.baseUrl()
|
|
const sameConfig = loadedPageGeneration === runner.pageGeneration
|
|
|
|
if (sameConfig && isSamePagePath(await webdriver.getCurrentUrl(), targetUrl)) {
|
|
const attr = await getBodyAttribute('loaded-dashboard')
|
|
if (attr != null && attr !== '') {
|
|
return
|
|
}
|
|
}
|
|
|
|
await webdriver.get(targetUrl)
|
|
await waitForDashboardLoaded()
|
|
loadedPageGeneration = runner.pageGeneration
|
|
}
|
|
|
|
async function isSidebarVisible () {
|
|
return isSidebarVisibleInBrowser()
|
|
}
|
|
|
|
export async function closeSidebar() {
|
|
if (await isSidebarVisible()) {
|
|
await webdriver.findElement(By.id('sidebar-toggler-button')).click()
|
|
}
|
|
|
|
await waitUntil('wait for sidebar to close', async () => {
|
|
return !(await isSidebarVisible())
|
|
})
|
|
}
|
|
|
|
export async function openSidebar() {
|
|
if (await isSidebarVisible()) {
|
|
return
|
|
}
|
|
|
|
await webdriver.findElement(By.id('sidebar-toggler-button')).click()
|
|
|
|
await waitUntil('wait for sidebar to open', async () => {
|
|
return await isSidebarVisible()
|
|
})
|
|
}
|
|
|
|
export async function getNavigationLinks() {
|
|
return await webdriver.findElements(By.css(sidebarNavLinksSelector))
|
|
}
|
|
|
|
export async function getNavigationLinkTitles () {
|
|
return webdriver.executeScript((selector) => {
|
|
return [...document.querySelectorAll(selector)].map((linkElement) => {
|
|
const title = linkElement.getAttribute('title')
|
|
if (title) {
|
|
return title
|
|
}
|
|
|
|
const anchor = linkElement.querySelector('a[href]')
|
|
return anchor ? anchor.textContent.trim() : ''
|
|
})
|
|
}, sidebarNavLinksSelector)
|
|
}
|
|
|
|
export async function waitForNavigationLinks (minimumCount = 1, timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await waitUntil(`wait for at least ${minimumCount} navigation links`, async () => {
|
|
const count = await countNavigationLinksInBrowser()
|
|
return count >= minimumCount
|
|
}, timeoutMs)
|
|
}
|
|
|
|
export async function getNavigationLinkTitle (linkElement) {
|
|
const title = await linkElement.getAttribute('title')
|
|
if (title) {
|
|
return title
|
|
}
|
|
|
|
const anchor = await linkElement.findElement(By.css('a[href]'))
|
|
return await anchor.getText()
|
|
}
|
|
|
|
export async function findSidebarNavHref (href) {
|
|
return await webdriver.findElements(By.css(sidebarCss(`a[href="${href}"]`)))
|
|
}
|
|
|
|
export async function requireExecutionDialogStatus (webdriver, expected) {
|
|
await waitUntil('wait for action to be running', async function () {
|
|
const dialogStatus = await webdriver.findElement(executionDialogStatusBy)
|
|
const actual = await dialogStatus.getText()
|
|
return actual === expected
|
|
})
|
|
}
|
|
|
|
export async function findExecutionDialog (webdriver) {
|
|
return webdriver.findElement(By.id('execution-results-popup'))
|
|
}
|
|
|
|
export async function getActionButton (webdriver, title) {
|
|
const buttons = await webdriver.findElements(By.css('[title="' + title + '"]'))
|
|
|
|
expect(buttons).to.have.length(1)
|
|
|
|
return buttons[0]
|
|
}
|
|
|
|
export async function getTerminalBuffer() {
|
|
try {
|
|
const output = await webdriver.executeScript(`
|
|
if (window.terminal && window.terminal.getBufferAsString) {
|
|
return window.terminal.getBufferAsString();
|
|
}
|
|
return null;
|
|
`)
|
|
return output
|
|
} catch (e) {
|
|
console.log('[getTerminalBuffer] Error:', e.message)
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function waitForCurrentUrl (predicate, timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await waitUntil('wait for url', async () => {
|
|
const url = await webdriver.getCurrentUrl()
|
|
return predicate(url)
|
|
}, timeoutMs)
|
|
}
|
|
|
|
export async function waitForSelectorCount (selector, minimumCount = 1, timeoutMs = DEFAULT_UI_WAIT_MS) {
|
|
await waitUntil(`wait for at least ${minimumCount} ${selector}`, async () => {
|
|
const count = await webdriver.executeScript(
|
|
(cssSelector) => document.querySelectorAll(cssSelector).length,
|
|
selector
|
|
)
|
|
return count >= minimumCount
|
|
}, timeoutMs)
|
|
}
|