diff --git a/.github/workflows/linting-and-tests.yml b/.github/workflows/linting-and-tests.yml index 866a6de2..7d1e298a 100644 --- a/.github/workflows/linting-and-tests.yml +++ b/.github/workflows/linting-and-tests.yml @@ -468,8 +468,12 @@ jobs: # hit 172.17.0.1 which proxies the request onto the host where port 30001 is the node port that is mapped # to the OnCall API ONCALL_API_URL: http://172.17.0.1:30001 - GRAFANA_USERNAME: oncall - GRAFANA_PASSWORD: oncall + GRAFANA_ADMIN_USERNAME: oncall + GRAFANA_ADMIN_PASSWORD: oncall + GRAFANA_EDITOR_USERNAME: editor + GRAFANA_EDITOR_PASSWORD: editor + GRAFANA_VIEWER_USERNAME: viewer + GRAFANA_VIEWER_PASSWORD: viewer MAILSLURP_API_KEY: ${{ secrets.MAILSLURP_API_KEY }} working-directory: ./grafana-plugin run: yarn test:integration diff --git a/dev/README.md b/dev/README.md index 212bb727..e8ae4fd2 100644 --- a/dev/README.md +++ b/dev/README.md @@ -192,7 +192,7 @@ To run these tests locally simply do the following: ```bash npx playwright install # install playwright dependencies -cp ./grafana-plugin/.env.example ./grafana-plugin/.env +cp ./grafana-plugin/integration-tests/.env.example ./grafana-plugin/integration-tests/.env # you may need to tweak the values in ./grafana-plugin/.env according to your local setup cd grafana-plugin yarn test:integration diff --git a/grafana-plugin/.env.example b/grafana-plugin/.env.example deleted file mode 100644 index 89211452..00000000 --- a/grafana-plugin/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# copy this file to ./.env and fill out the values according to your local setup - -# for integration test purposes -BASE_URL=http://localhost:3000 -ONCALL_API_URL=http://host.docker.internal:8080/ -GRAFANA_USERNAME=oncall -GRAFANA_PASSWORD=oncall -IS_OPEN_SOURCE=True diff --git a/grafana-plugin/integration-tests/.auth/.gitignore b/grafana-plugin/integration-tests/.auth/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/grafana-plugin/integration-tests/.auth/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/grafana-plugin/integration-tests/.env.example b/grafana-plugin/integration-tests/.env.example new file mode 100644 index 00000000..530491bb --- /dev/null +++ b/grafana-plugin/integration-tests/.env.example @@ -0,0 +1,9 @@ +BASE_URL=http://localhost:3000 +ONCALL_API_URL=http://host.docker.internal:8080/ +GRAFANA_VIEWER_USERNAME=viewer +GRAFANA_VIEWER_PASSWORD=viewer +GRAFANA_EDITOR_USERNAME=editor +GRAFANA_EDITOR_PASSWORD=editor +GRAFANA_ADMIN_USERNAME=oncall +GRAFANA_ADMIN_PASSWORD=oncall +IS_OPEN_SOURCE=True diff --git a/grafana-plugin/integration-tests/alerts/onCallSchedule.test.ts b/grafana-plugin/integration-tests/alerts/onCallSchedule.test.ts index 3ba425a9..7cb7686d 100644 --- a/grafana-plugin/integration-tests/alerts/onCallSchedule.test.ts +++ b/grafana-plugin/integration-tests/alerts/onCallSchedule.test.ts @@ -1,19 +1,20 @@ -import { test } from '@playwright/test'; +import { test } from '../fixtures'; import { verifyThatAlertGroupIsTriggered } from '../utils/alertGroup'; import { createEscalationChain, EscalationStep } from '../utils/escalationChain'; import { generateRandomValue } from '../utils/forms'; import { createIntegrationAndSendDemoAlert } from '../utils/integrations'; import { createOnCallSchedule } from '../utils/schedule'; -test('we can create an oncall schedule + receive an alert', async ({ page }) => { +test('we can create an oncall schedule + receive an alert', async ({ adminRolePage }) => { // this test does a lot of stuff, lets give it adequate time to do its thing test.slow(); + const { page, userName } = adminRolePage; const escalationChainName = generateRandomValue(); const integrationName = generateRandomValue(); const onCallScheduleName = generateRandomValue(); - await createOnCallSchedule(page, onCallScheduleName); + await createOnCallSchedule(page, onCallScheduleName, userName); await createEscalationChain( page, escalationChainName, diff --git a/grafana-plugin/integration-tests/alerts/sms.test.ts b/grafana-plugin/integration-tests/alerts/sms.test.ts index 070eef89..7da7f212 100644 --- a/grafana-plugin/integration-tests/alerts/sms.test.ts +++ b/grafana-plugin/integration-tests/alerts/sms.test.ts @@ -1,5 +1,4 @@ -import { test, expect } from '@playwright/test'; -import { GRAFANA_USERNAME } from '../utils/constants'; +import { test, expect } from '../fixtures'; import { createEscalationChain, EscalationStep } from '../utils/escalationChain'; import { generateRandomValue } from '../utils/forms'; import { createIntegrationAndSendDemoAlert } from '../utils/integrations'; @@ -7,14 +6,15 @@ import { waitForSms } from '../utils/phone'; import { configureUserNotificationSettings, verifyUserPhoneNumber } from '../utils/userSettings'; // TODO: enable once we've signed up for a MailSlurp account to receieve SMSes -test.skip('we can verify our phone number + receive an SMS alert', async ({ page }) => { +test.skip('we can verify our phone number + receive an SMS alert', async ({ adminRolePage }) => { + const { page, userName } = adminRolePage; const escalationChainName = generateRandomValue(); const integrationName = generateRandomValue(); await verifyUserPhoneNumber(page); await configureUserNotificationSettings(page, 'SMS'); - await createEscalationChain(page, escalationChainName, EscalationStep.NotifyUsers, GRAFANA_USERNAME); + await createEscalationChain(page, escalationChainName, EscalationStep.NotifyUsers, userName); await createIntegrationAndSendDemoAlert(page, integrationName, escalationChainName); // wait for the SMS alert notification to arrive diff --git a/grafana-plugin/integration-tests/escalationChains/searching.test.ts b/grafana-plugin/integration-tests/escalationChains/searching.test.ts index 5013f00f..4d61ae48 100644 --- a/grafana-plugin/integration-tests/escalationChains/searching.test.ts +++ b/grafana-plugin/integration-tests/escalationChains/searching.test.ts @@ -1,4 +1,4 @@ -import { test, expect, Page } from '@playwright/test'; +import { test, expect, Page } from '../fixtures'; import { generateRandomValue } from '../utils/forms'; import { createEscalationChain } from '../utils/escalationChain'; @@ -16,7 +16,9 @@ const assertEscalationChainSearchWorks = async ( }; // TODO: add tests for the new filtering. Commented out as this search doesn't exist anymore -test.skip('searching allows case-insensitive partial matches', async ({ page }) => { +test.skip('searching allows case-insensitive partial matches', async ({ adminRolePage }) => { + const { page } = adminRolePage; + const escalationChainName = `${generateRandomValue()} ${generateRandomValue()}`; const [firstHalf, secondHalf] = escalationChainName.split(' '); diff --git a/grafana-plugin/integration-tests/fixtures.ts b/grafana-plugin/integration-tests/fixtures.ts new file mode 100644 index 00000000..79b2d020 --- /dev/null +++ b/grafana-plugin/integration-tests/fixtures.ts @@ -0,0 +1,53 @@ +import { test as base, Page } from '@playwright/test'; + +import { GRAFANA_ADMIN_USERNAME, GRAFANA_EDITOR_USERNAME, GRAFANA_VIEWER_USERNAME } from './utils/constants'; +import { VIEWER_USER_STORAGE_STATE, EDITOR_USER_STORAGE_STATE, ADMIN_USER_STORAGE_STATE } from '../playwright.config'; + +export class BaseRolePage { + page: Page; + userName: string; + + constructor(page: Page) { + this.page = page; + } +} + +class ViewerRolePage extends BaseRolePage { + userName = GRAFANA_VIEWER_USERNAME; +} + +class EditorRolePage extends BaseRolePage { + userName = GRAFANA_EDITOR_USERNAME; +} + +class AdminRolePage extends BaseRolePage { + userName = GRAFANA_ADMIN_USERNAME; +} + +type Fixtures = { + viewerRolePage: ViewerRolePage; + editorRolePage: EditorRolePage; + adminRolePage: AdminRolePage; +}; + +export * from '@playwright/test'; +export const test = base.extend({ + viewerRolePage: async ({ browser }, use) => { + const context = await browser.newContext({ storageState: VIEWER_USER_STORAGE_STATE }); + const page = new ViewerRolePage(await context.newPage()); + await use(page); + await context.close(); + }, + editorRolePage: async ({ browser }, use) => { + const context = await browser.newContext({ storageState: EDITOR_USER_STORAGE_STATE }); + const page = new EditorRolePage(await context.newPage()); + await use(page); + await context.close(); + }, + adminRolePage: async ({ browser }, use) => { + const context = await browser.newContext({ storageState: ADMIN_USER_STORAGE_STATE }); + const page = new AdminRolePage(await context.newPage()); + await use(page); + await context.close(); + }, +}); diff --git a/grafana-plugin/integration-tests/globalSetup.ts b/grafana-plugin/integration-tests/globalSetup.ts index 5881971a..c7c4227a 100644 --- a/grafana-plugin/integration-tests/globalSetup.ts +++ b/grafana-plugin/integration-tests/globalSetup.ts @@ -1,37 +1,57 @@ -import { test as setup, chromium, FullConfig, expect, Page, BrowserContext, APIResponse } from '@playwright/test'; +import { test as setup, chromium, expect, Page, BrowserContext, FullConfig, APIRequestContext } from '@playwright/test'; -import { BASE_URL, GRAFANA_PASSWORD, GRAFANA_USERNAME, IS_OPEN_SOURCE, ONCALL_API_URL } from './utils/constants'; +import GrafanaAPIClient from './utils/clients/grafana'; +import { + GRAFANA_ADMIN_PASSWORD, + GRAFANA_ADMIN_USERNAME, + GRAFANA_EDITOR_PASSWORD, + GRAFANA_EDITOR_USERNAME, + GRAFANA_VIEWER_PASSWORD, + GRAFANA_VIEWER_USERNAME, + IS_CLOUD, + IS_OPEN_SOURCE, + ONCALL_API_URL, +} from './utils/constants'; import { clickButton, getInputByName } from './utils/forms'; import { goToGrafanaPage } from './utils/navigation'; -import { STORAGE_STATE } from '../playwright.config'; +import { VIEWER_USER_STORAGE_STATE, EDITOR_USER_STORAGE_STATE, ADMIN_USER_STORAGE_STATE } from '../playwright.config'; +import { OrgRole } from '@grafana/data'; -const IS_CLOUD = !IS_OPEN_SOURCE; -const GLOBAL_SETUP_RETRIES = 3; +const grafanaApiClient = new GrafanaAPIClient(GRAFANA_ADMIN_USERNAME, GRAFANA_ADMIN_PASSWORD); -const makeGrafanaLoginRequest = async (browserContext: BrowserContext): Promise => - browserContext.request.post(`${BASE_URL}/login`, { - data: { - user: GRAFANA_USERNAME, - password: GRAFANA_PASSWORD, - }, - }); +type UserCreationSettings = { + adminAuthedRequest: APIRequestContext; + role: OrgRole; +}; -const pollGrafanaInstanceUntilItIsHealthy = async (browserContext: BrowserContext): Promise => { - console.log('Polling the grafana instance to make sure it is healthy'); - - const res = await makeGrafanaLoginRequest(browserContext); - - if (!res.ok()) { - console.log(`Grafana instance is unavailable. Got HTTP ${res.status()}. Will wait 5 seconds and then try again`); - await new Promise((resolve) => setTimeout(resolve, 5000)); - return pollGrafanaInstanceUntilItIsHealthy(browserContext); +const generateLoginStorageStateAndOptionallCreateUser = async ( + config: FullConfig, + userName: string, + password: string, + storageStateFileLocation: string, + userCreationSettings?: UserCreationSettings, + closeContext = false +): Promise => { + if (userCreationSettings !== undefined && IS_OPEN_SOURCE) { + const { adminAuthedRequest, role } = userCreationSettings; + await grafanaApiClient.idempotentlyCreateUserWithRole(adminAuthedRequest, userName, password, role); } - console.log('Grafana instance is available'); - return true; + + const { headless } = config.projects[0]!.use; + const browser = await chromium.launch({ headless, slowMo: headless ? 0 : 100 }); + const browserContext = await browser.newContext(); + + await grafanaApiClient.login(browserContext.request, userName, password); + await browserContext.storageState({ path: storageStateFileLocation }); + + if (closeContext) { + await browserContext.close(); + } + return browserContext; }; /** - * go to config page and wait for plugin icon to be available on left-hand navigation + go to config page and wait for plugin icon to be available on left-hand navigation */ const configureOnCallPlugin = async (page: Page): Promise => { /** @@ -66,55 +86,7 @@ const configureOnCallPlugin = async (page: Page): Promise => { * Borrowed from our friends on the Incident team * https://github.com/grafana/incident/blob/main/plugin/e2e/global-setup.ts */ -const globalSetup = async (config: FullConfig): Promise => { - const { headless } = config.projects[0]!.use; - const browser = await chromium.launch({ headless, slowMo: headless ? 0 : 100 }); - const browserContext = await browser.newContext(); - - if (IS_CLOUD) { - /** - * check that the grafana instance is available. If HTTP 503 is returned it means the - * instance is currently unavailable. Poll until it is available - */ - await pollGrafanaInstanceUntilItIsHealthy(browserContext); - } - - const res = await makeGrafanaLoginRequest(browserContext); - - expect(res.ok()).toBeTruthy(); - await browserContext.storageState({ path: STORAGE_STATE }); - - // make sure the plugin has been configured - const page = await browserContext.newPage(); - - if (IS_OPEN_SOURCE) { - // plugin configuration can safely be skipped for cloud environments - await configureOnCallPlugin(page); - } - - await browserContext.close(); -}; - -/** - * Let's retry global setup, in the event that it fails due to an oncall-engine/oncall-celery backend error. - * Sometimes the sync endpoint will randomly return HTTP 500. - * See here for an example CI job which failed global setup - * https://github.com/grafana/oncall/actions/runs/5062712137/jobs/9088529416#step:19:2536 - * - * References on retrying playwright global setup - * https://github.com/microsoft/playwright/discussions/11371 - */ -const globalSetupWithRetries = async (config: FullConfig): Promise => { - for (let i = 0; i < GLOBAL_SETUP_RETRIES - 1; i++) { - try { - return await globalSetup(config); - } catch (e) {} - } - // One last time, throwing an error if it fails. - await globalSetup(config); -}; - -setup('Configure Grafana OnCall plugin', async ({}, { config }) => { +setup('Configure Grafana OnCall plugin', async ({ request }, { config }) => { /** * Unconditionally marks the setup as "slow", giving it triple the default timeout. * This is mostly useful for the rare case for Cloud Grafana instances where the instance may be down/unavailable @@ -122,5 +94,47 @@ setup('Configure Grafana OnCall plugin', async ({}, { config }) => { */ setup.slow(); - await globalSetupWithRetries(config); + if (IS_CLOUD) { + await grafanaApiClient.pollInstanceUntilItIsHealthy(request); + } + + const adminBrowserContext = await generateLoginStorageStateAndOptionallCreateUser( + config, + GRAFANA_ADMIN_USERNAME, + GRAFANA_ADMIN_PASSWORD, + ADMIN_USER_STORAGE_STATE + ); + const adminPage = await adminBrowserContext.newPage(); + const { request: adminAuthedRequest } = adminBrowserContext; + + await generateLoginStorageStateAndOptionallCreateUser( + config, + GRAFANA_EDITOR_USERNAME, + GRAFANA_EDITOR_PASSWORD, + EDITOR_USER_STORAGE_STATE, + { + adminAuthedRequest, + role: OrgRole.Editor, + }, + true + ); + + await generateLoginStorageStateAndOptionallCreateUser( + config, + GRAFANA_VIEWER_USERNAME, + GRAFANA_VIEWER_PASSWORD, + VIEWER_USER_STORAGE_STATE, + { + adminAuthedRequest, + role: OrgRole.Viewer, + }, + true + ); + + if (IS_OPEN_SOURCE) { + // plugin configuration can safely be skipped for cloud environments + await configureOnCallPlugin(adminPage); + } + + await adminBrowserContext.close(); }); diff --git a/grafana-plugin/integration-tests/integrations/uniqueIntegrationNames.test.ts b/grafana-plugin/integration-tests/integrations/uniqueIntegrationNames.test.ts index e3c8df80..be27aa86 100644 --- a/grafana-plugin/integration-tests/integrations/uniqueIntegrationNames.test.ts +++ b/grafana-plugin/integration-tests/integrations/uniqueIntegrationNames.test.ts @@ -1,7 +1,8 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from '../fixtures'; import { openCreateIntegrationModal } from '../utils/integrations'; -test('integrations have unique names', async ({ page }) => { +test('integrations have unique names', async ({ adminRolePage }) => { + const { page } = adminRolePage; await openCreateIntegrationModal(page); const integrationNames = await page.getByTestId('integration-display-name').allInnerTexts(); diff --git a/grafana-plugin/integration-tests/schedules/addOverride.test.ts b/grafana-plugin/integration-tests/schedules/addOverride.test.ts index f76b6ef9..2ee99494 100644 --- a/grafana-plugin/integration-tests/schedules/addOverride.test.ts +++ b/grafana-plugin/integration-tests/schedules/addOverride.test.ts @@ -1,11 +1,13 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from '../fixtures'; import { clickButton, generateRandomValue } from '../utils/forms'; import { createOnCallSchedule, getOverrideFormDateInputs } from '../utils/schedule'; import dayjs from 'dayjs'; -test('default dates in override creation modal are correct', async ({ page }) => { +test('default dates in override creation modal are correct', async ({ adminRolePage }) => { + const { page, userName } = adminRolePage; + const onCallScheduleName = generateRandomValue(); - await createOnCallSchedule(page, onCallScheduleName); + await createOnCallSchedule(page, onCallScheduleName, userName); await clickButton({ page, buttonText: 'Add override' }); diff --git a/grafana-plugin/integration-tests/schedules/quality.test.ts b/grafana-plugin/integration-tests/schedules/quality.test.ts index 4e010162..bcf55442 100644 --- a/grafana-plugin/integration-tests/schedules/quality.test.ts +++ b/grafana-plugin/integration-tests/schedules/quality.test.ts @@ -1,10 +1,12 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from '../fixtures'; import { generateRandomValue } from '../utils/forms'; import { createOnCallSchedule } from '../utils/schedule'; -test('check schedule quality for simple 1-user schedule', async ({ page }) => { +test('check schedule quality for simple 1-user schedule', async ({ adminRolePage }) => { + const { page, userName } = adminRolePage; const onCallScheduleName = generateRandomValue(); - await createOnCallSchedule(page, onCallScheduleName); + + await createOnCallSchedule(page, onCallScheduleName, userName); /** * this page.reload() call is a hack to temporarily get around this issue diff --git a/grafana-plugin/integration-tests/users/viewUsers.test.ts b/grafana-plugin/integration-tests/users/viewUsers.test.ts new file mode 100644 index 00000000..f545097a --- /dev/null +++ b/grafana-plugin/integration-tests/users/viewUsers.test.ts @@ -0,0 +1,34 @@ +import { test, expect, Page } from '../fixtures'; +import { goToOnCallPage } from '../utils/navigation'; + +test.describe('view list of users', () => { + const testFlow = async (page: Page, isAllowedToView = true): Promise => { + await goToOnCallPage(page, 'users'); + + if (isAllowedToView) { + const usersTableElement = page.getByTestId('users-table'); + await usersTableElement.waitFor({ state: 'visible' }); + + const userRowsContext = await usersTableElement.locator('tbody > tr').allTextContents(); + expect(userRowsContext.length).toBeGreaterThan(0); + } else { + const missingPermissionsMessageElement = page.getByTestId('view-users-missing-permission-message'); + await missingPermissionsMessageElement.waitFor({ state: 'visible' }); + + const missingPermissionMessage = await missingPermissionsMessageElement.textContent(); + expect(missingPermissionMessage).toMatch(/You are missing the .* to be able to view OnCall users/); + } + }; + + test('admin is allowed to', async ({ adminRolePage }) => { + await testFlow(adminRolePage.page); + }); + + test('editor is allowed to', async ({ editorRolePage }) => { + await testFlow(editorRolePage.page); + }); + + test('viewer is not allowed to', async ({ viewerRolePage }) => { + await testFlow(viewerRolePage.page, false); + }); +}); diff --git a/grafana-plugin/integration-tests/utils/clients/grafana.ts b/grafana-plugin/integration-tests/utils/clients/grafana.ts new file mode 100644 index 00000000..21274100 --- /dev/null +++ b/grafana-plugin/integration-tests/utils/clients/grafana.ts @@ -0,0 +1,116 @@ +import { OrgRole } from '@grafana/data'; +import { expect, APIRequestContext } from '@playwright/test'; + +import { BASE_URL } from '../constants'; + +type UsersLookupResponse = { + id: number; +}; + +type CreateUserResponse = { + id: number; +}; + +class GrafanaApiException extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; + } +} + +export default class GrafanaAPIClient { + userName: string; + password: string; + + constructor(userName: string, password: string) { + this.userName = userName; + this.password = password; + } + + get requestHeaders() { + const base64encodedCredentials = Buffer.from(`${this.userName}:${this.password}`).toString('base64'); + return { + Authorization: `Basic ${base64encodedCredentials}`, + }; + } + + /** + * check that the grafana instance is available. If HTTP 503 is returned it means the + * instance is currently unavailable. Poll until it is available + */ + pollInstanceUntilItIsHealthy = async (request: APIRequestContext): Promise => { + console.log('Polling the grafana instance to make sure it is healthy'); + + const res = await request.get(`${BASE_URL}/api/health`); + + if (!res.ok()) { + console.log(`Grafana instance is unavailable. Got HTTP ${res.status()}. Will wait 5 seconds and then try again`); + await new Promise((resolve) => setTimeout(resolve, 5000)); + return this.pollInstanceUntilItIsHealthy(request); + } + console.log('Grafana instance is available'); + return true; + }; + + getUserIdByUsername = async (request: APIRequestContext, userName: string): Promise => { + const res = await request.get(`${BASE_URL}/api/users/lookup?loginOrEmail=${userName}`, { + headers: this.requestHeaders, + }); + expect(res.ok()).toBeTruthy(); + const responseData: UsersLookupResponse = await res.json(); + return responseData.id; + }; + + updateUserRole = async (request: APIRequestContext, userId: number, role: OrgRole): Promise => { + const res = await request.patch(`${BASE_URL}/api/org/users/${userId}`, { + data: { role }, + headers: this.requestHeaders, + }); + expect(res.ok()).toBeTruthy(); + }; + + /** + * Should return one of the following two responses: + * - HTTP 200 - user successfully created + * - HTTP 412 - user w/ this username already exists (fine to ignore this) + */ + idempotentlyCreateUserWithRole = async ( + request: APIRequestContext, + userName: string, + password: string, + role: OrgRole + ) => { + const res = await request.post(`${BASE_URL}/api/admin/users`, { + data: { + name: `e2e user - ${userName}`, + login: userName, + password, + }, + }); + + let userId: number; + const responseCode = res.status(); + + if (responseCode === 200) { + // user was just created + const respJson: CreateUserResponse = await res.json(); + userId = respJson.id; + } else if (responseCode == 412) { + // user already exists, go fetch their user id + userId = await this.getUserIdByUsername(request, userName); + } else { + throw new GrafanaApiException( + `Received unexpected status code while trying to idempotently create user - HTTP${responseCode}: ${await res.body()}` + ); + } + + await this.updateUserRole(request, userId, role); + }; + + login = async (request: APIRequestContext, userName: string, password: string) => { + const res = await request.post(`${BASE_URL}/login`, { + data: { user: userName, password }, + }); + expect(res.ok()).toBeTruthy(); + }; +} diff --git a/grafana-plugin/integration-tests/utils/constants.ts b/grafana-plugin/integration-tests/utils/constants.ts index 7e9a5998..97fcd3b7 100644 --- a/grafana-plugin/integration-tests/utils/constants.ts +++ b/grafana-plugin/integration-tests/utils/constants.ts @@ -1,6 +1,13 @@ export const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; export const ONCALL_API_URL = process.env.ONCALL_API_URL || 'http://host.docker.internal:8080'; -export const GRAFANA_USERNAME = process.env.GRAFANA_USERNAME || 'oncall'; -export const GRAFANA_PASSWORD = process.env.GRAFANA_PASSWORD || 'oncall'; export const MAILSLURP_API_KEY = process.env.MAILSLURP_API_KEY; + +export const GRAFANA_VIEWER_USERNAME = process.env.GRAFANA_VIEWER_USERNAME || 'viewer'; +export const GRAFANA_VIEWER_PASSWORD = process.env.GRAFANA_VIEWER_PASSWORD || 'viewer'; +export const GRAFANA_EDITOR_USERNAME = process.env.GRAFANA_EDITOR_USERNAME || 'editor'; +export const GRAFANA_EDITOR_PASSWORD = process.env.GRAFANA_EDITOR_PASSWORD || 'editor'; +export const GRAFANA_ADMIN_USERNAME = process.env.GRAFANA_ADMIN_USERNAME || 'oncall'; +export const GRAFANA_ADMIN_PASSWORD = process.env.GRAFANA_ADMIN_PASSWORD || 'oncall'; + export const IS_OPEN_SOURCE = (process.env.IS_OPEN_SOURCE || 'true').toLowerCase() === 'true'; +export const IS_CLOUD = !IS_OPEN_SOURCE; diff --git a/grafana-plugin/integration-tests/utils/schedule.ts b/grafana-plugin/integration-tests/utils/schedule.ts index d3106807..9c30c746 100644 --- a/grafana-plugin/integration-tests/utils/schedule.ts +++ b/grafana-plugin/integration-tests/utils/schedule.ts @@ -1,10 +1,9 @@ import { Page } from '@playwright/test'; -import { GRAFANA_USERNAME } from './constants'; -import { clickButton, fillInInput, selectDropdownValue, selectValuePickerValue } from './forms'; +import { clickButton, fillInInput, selectDropdownValue } from './forms'; import { goToOnCallPage } from './navigation'; import dayjs from 'dayjs'; -export const createOnCallSchedule = async (page: Page, scheduleName: string): Promise => { +export const createOnCallSchedule = async (page: Page, scheduleName: string, userName: string): Promise => { // go to the schedules page await goToOnCallPage(page, 'schedules'); @@ -24,7 +23,7 @@ export const createOnCallSchedule = async (page: Page, scheduleName: string): Pr page, selectType: 'grafanaSelect', placeholderText: 'Add user', - value: GRAFANA_USERNAME, + value: userName, }); await clickButton({ page, buttonText: 'Create' }); diff --git a/grafana-plugin/playwright.config.ts b/grafana-plugin/playwright.config.ts index 8ea67ebc..bab2ea80 100644 --- a/grafana-plugin/playwright.config.ts +++ b/grafana-plugin/playwright.config.ts @@ -7,9 +7,11 @@ import { devices } from '@playwright/test'; * Read environment variables from file. * https://github.com/motdotla/dotenv */ -require('dotenv').config(); +require('dotenv').config({ path: path.resolve(process.cwd(), 'integration-tests/.env') }); -export const STORAGE_STATE = path.join(__dirname, 'integration-tests/storageState.json'); +export const VIEWER_USER_STORAGE_STATE = path.join(__dirname, 'integration-tests/.auth/viewer.json'); +export const EDITOR_USER_STORAGE_STATE = path.join(__dirname, 'integration-tests/.auth/editor.json'); +export const ADMIN_USER_STORAGE_STATE = path.join(__dirname, 'integration-tests/.auth/admin.json'); /** * See https://playwright.dev/docs/test-configuration. @@ -62,7 +64,6 @@ const config: PlaywrightTestConfig = { name: 'chromium', use: { ...devices['Desktop Chrome'], - storageState: STORAGE_STATE, }, dependencies: ['setup'], }, @@ -70,7 +71,6 @@ const config: PlaywrightTestConfig = { name: 'firefox', use: { ...devices['Desktop Firefox'], - storageState: STORAGE_STATE, }, dependencies: ['setup'], }, @@ -78,7 +78,6 @@ const config: PlaywrightTestConfig = { name: 'webkit', use: { ...devices['Desktop Safari'], - storageState: STORAGE_STATE, }, dependencies: ['setup'], }, diff --git a/grafana-plugin/src/pages/users/Users.tsx b/grafana-plugin/src/pages/users/Users.tsx index 47528812..b44bed61 100644 --- a/grafana-plugin/src/pages/users/Users.tsx +++ b/grafana-plugin/src/pages/users/Users.tsx @@ -224,6 +224,7 @@ class Users extends React.Component { { profile } + data-testid="view-users-missing-permission-message" severity="info" /> )}