[UI] Playwright Test - Tools Workflows (#12430) (#12440)

* adds clipboard permissions for playwright tests

* fixes access control link in playwright init setup

* adds base page class with method for dismissing flash notifications

* fixes nav links in playwright kv test and adds base page for dismissing flash messages

* adds playwright test for tools workflows

Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
This commit is contained in:
Vault Automation 2026-02-19 12:17:31 -05:00 committed by GitHub
parent 978293788d
commit 74237d6daa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 96 additions and 3 deletions

View file

@ -41,7 +41,7 @@ setup('initialize vault and setup user for testing', async ({ page, userType })
await page.getByRole('button', { name: 'Sign in' }).click();
// create a policy for a specific user persona
// defaults to superuser but should be passed in via the project config in playwright.config.ts
await page.getByRole('link', { name: 'Access', exact: true }).click();
await page.getByRole('link', { name: 'Access control', exact: true }).click();
await page.getByRole('link', { name: 'Create ACL policy' }).click();
await page.getByRole('textbox', { name: 'Policy name' }).fill(userType);
await page.getByRole('radio', { name: 'Code editor' }).check();

19
ui/e2e/pages/base.ts Normal file
View file

@ -0,0 +1,19 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import { Page } from '@playwright/test';
export class BasePage {
constructor(protected page: Page) {}
// remove all flash messages by clicking the dismiss button until there are no more
// this is useful if many are rendered over top of a button preventing click in a test
async dismissFlashMessages() {
const locator = this.page.getByRole('button', { name: 'Dismiss' });
// use a while loop because clicking one might cause the next one to shift or re-render.
while ((await locator.count()) > 0) {
await locator.first().click();
}
}
}

View file

@ -4,11 +4,13 @@
*/
import { test, expect } from '@playwright/test';
import { BasePage } from '../../pages/base';
test('kvv2 workflow', async ({ page }) => {
const basePage = new BasePage(page);
await page.goto('dashboard');
// enable kv secrets engine
await page.getByRole('link', { name: 'Secrets Engines' }).click();
await page.getByRole('link', { name: 'Secrets', exact: true }).click();
await page.getByRole('link', { name: 'Enable new engine' }).click();
await page.locator('div').filter({ hasText: 'KV' }).nth(4).click();
await page.getByRole('textbox', { name: 'Path' }).click();
@ -20,7 +22,7 @@ test('kvv2 workflow', async ({ page }) => {
'No secrets yet When created, secrets will be listed here. Create a secret to get started.'
);
// verify that the kv engine appears in the list view
await page.getByRole('link', { name: 'Secrets Engines' }).click();
await page.getByLabel('Secrets Navigation Links').getByRole('link', { name: 'Secrets engines' }).click();
await page.getByRole('link', { name: 'kv-test/' }).click();
// create a secret
await page.getByRole('link', { name: 'Create secret' }).click();
@ -54,6 +56,8 @@ test('kvv2 workflow', async ({ page }) => {
await page.getByRole('link', { name: 'Version History' }).click();
await expect(page.locator('section')).toContainText('Version 1');
await expect(page.locator('section')).toContainText('Current');
// there are a stack of flash messages that are blocking the manage version button from being clicked
await basePage.dismissFlashMessages();
await page.getByRole('button', { name: 'Manage version' }).click();
await page.getByRole('link', { name: 'Create new version from 1', exact: true }).click();
await page.getByRole('textbox', { name: 'key' }).first().fill('bar-v2');

View file

@ -0,0 +1,69 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import { test, expect } from '@playwright/test';
import { BasePage } from '../../pages/base';
test('tools workflow', async ({ page }) => {
const basePage = new BasePage(page);
await page.goto('dashboard');
// wrap data
await page.getByRole('link', { name: 'Operational tools' }).click();
await page.getByRole('navigation', { name: 'toolbar filters' }).locator('label').click();
await page.getByRole('textbox', { name: 'Key' }).fill('foo');
await page.getByRole('textbox', { name: 'Value' }).fill('bar');
await page.locator('label').filter({ hasText: 'Wrap TTL Vault will use the' }).click();
await page.getByRole('textbox', { name: 'Number of units' }).fill('20');
await page.getByLabel('ttl-unit').selectOption('h');
await page.getByRole('button', { name: 'Wrap data' }).click();
await page.getByRole('button', { name: 'copy hvs.' }).click();
// access the clipboard to get the copied token value
let clipboardValue = await page.evaluate(() => navigator.clipboard.readText());
// lookup token
await page.getByRole('link', { name: 'Lookup' }).click();
await page.getByRole('textbox', { name: 'Wrapped token' }).fill(clipboardValue);
await page.getByRole('button', { name: 'Lookup token' }).click();
await expect(page.getByRole('heading', { name: 'Lookup token' })).toContainText('Lookup token');
await expect(page.locator('section')).toContainText('about 20 hours');
// rewrap token
await page.getByRole('link', { name: 'Rewrap' }).click();
await page.getByRole('textbox', { name: 'Wrapped token' }).fill(clipboardValue);
await page.getByRole('button', { name: 'Rewrap token' }).click();
await expect(page.locator('label')).toContainText('Rewrapped token');
await page.getByRole('button', { name: 'copy hvs.' }).click();
// access the clipboard to get the rewrapped token value
clipboardValue = await page.evaluate(() => navigator.clipboard.readText());
// unwrap token
await page.getByRole('link', { name: 'Unwrap' }).click();
await page.getByRole('textbox', { name: 'Wrapped token' }).fill(clipboardValue);
await page.getByRole('button', { name: 'Unwrap data' }).click();
await expect(page.getByRole('code')).toContainText('{ "foo": "bar" }');
// generate random bytes
await page.getByRole('link', { name: 'Random' }).click();
await page.getByRole('spinbutton', { name: 'Number of bytes' }).fill('64');
await page.getByLabel('Output format').selectOption('hex');
await page.getByRole('button', { name: 'Generate' }).click();
await expect(page.getByRole('button', { name: 'copy' })).toBeVisible();
// hash
await page.getByRole('link', { name: 'Hash', exact: true }).click();
await page.getByRole('textbox', { name: 'Input' }).fill('foobar');
await page.getByLabel('Algorithm').selectOption('sha2-512');
await page.getByLabel('Output format').selectOption('hex');
// there are a stack of flash messages that are blocking the encode button from being clicked
await basePage.dismissFlashMessages();
await page.getByRole('button', { name: 'Encode to base64' }).click();
await page.getByRole('button', { name: 'Hash' }).click();
await expect(page.getByLabel('copy')).toContainText(
'0a50261ebd1a390fed2bf326f2673c145582a6342d523204973d0219337f81616a8069b012587cf5635f6925f1b56c360230c19b273500ee013e030601bf2425'
);
// API explorer
await page.getByRole('link', { name: 'API explorer' }).click();
await expect(page.getByRole('heading', { name: 'API explorer' })).toContainText('API explorer');
await page.getByRole('textbox', { name: 'Filter by tag' }).fill('lookup-accessor');
await expect(page.locator('#operations-0-tokenLookUpAccessor')).toContainText(
'/auth/token/lookup-accessor'
);
await expect(page.locator('#operations-0-tokenLookUpAccessor')).toContainText('tokenLookUpAccessor');
});

View file

@ -67,6 +67,7 @@ export default defineConfig<UserSetupOptions>({
storageState: fs.existsSync(sessionFile) ? sessionFile : undefined,
// start at port 8204 and increment for each project to allow them to run concurrently without conflicts
baseURL: getURL(index),
permissions: ['clipboard-read', 'clipboard-write'],
},
};
}),