mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
Every first-party Python caller and test now names the mount the API actually answers on. Mechanical: URL literals only, and only where the /api/ was ours -- a preceding word character means it was a module path like insights/api/ or a third-party host like slack.com/api/, and those are untouched. Left saying /api/, each for a reason rather than for safety: - products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints - services/llm-gateway deploys separately and is under a code freeze, so moving its two calls would break it during the skew - the mcp_store and Slack/Teams redirect_uris are registered with third parties and must byte-match what was registered - the Wizard and Raycast client-metadata paths are OAuth client_ids - /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the ingress routes to the Rust fleets, held by released SDK versions Test URLconfs moved with the requests that call them, and the Datastore user-selection fixtures now carry the leading slash that request.path has.
578 lines
25 KiB
TypeScript
578 lines
25 KiB
TypeScript
import { Page } from '@playwright/test'
|
|
|
|
import { DashboardPage } from '../../page-models/dashboardPage'
|
|
import { InsightPage } from '../../page-models/insightPage'
|
|
import { randomString } from '../../utils'
|
|
import { PlaywrightWorkspaceSetupResult, expect, test } from '../../utils/workspace-test-base'
|
|
|
|
/**
|
|
* Creates a saved Trends insight via the API (no UI interaction).
|
|
* This avoids flakiness from chart rendering timeouts under CI load.
|
|
*/
|
|
async function createSavedTrendsInsight(
|
|
page: Page,
|
|
insightName: string,
|
|
workspace: PlaywrightWorkspaceSetupResult
|
|
): Promise<void> {
|
|
const response = await page.request.post(`/v1/projects/${workspace.team_id}/insights/`, {
|
|
headers: {
|
|
Authorization: `Bearer ${workspace.personal_api_key}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
data: {
|
|
name: insightName,
|
|
query: {
|
|
kind: 'InsightVizNode',
|
|
source: {
|
|
kind: 'TrendsQuery',
|
|
series: [{ kind: 'EventsNode', event: '$pageview' }],
|
|
},
|
|
},
|
|
},
|
|
})
|
|
expect(response.ok()).toBe(true)
|
|
}
|
|
|
|
test.describe('Dashboards', () => {
|
|
let workspace: PlaywrightWorkspaceSetupResult | null = null
|
|
|
|
test.beforeAll(async ({ playwrightSetup }) => {
|
|
workspace = await playwrightSetup.createWorkspace({ use_current_time: true, skip_onboarding: true })
|
|
})
|
|
|
|
test.beforeEach(async ({ page, playwrightSetup }) => {
|
|
await playwrightSetup.login(page, workspace!)
|
|
})
|
|
|
|
test('Can create a new dashboard with an insight', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const dashboardName = randomString('dash-edit')
|
|
const insightName = randomString('dash-trends')
|
|
|
|
await test.step('create a saved Trends insight', async () => {
|
|
await createSavedTrendsInsight(page, insightName, workspace!)
|
|
})
|
|
|
|
await test.step('create a dashboard', async () => {
|
|
await dashboard.createNew(dashboardName)
|
|
})
|
|
|
|
await test.step('add the insight to the dashboard', async () => {
|
|
await dashboard.addInsightToNewDashboard(insightName)
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test('Editing an insight updates the dashboard tile', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const insight = new InsightPage(page)
|
|
const insightName = randomString('dash-trends')
|
|
const updatedName = randomString('dash-updated')
|
|
let dashboardUrl: string
|
|
|
|
await test.step('create a saved Trends insight', async () => {
|
|
await createSavedTrendsInsight(page, insightName, workspace!)
|
|
})
|
|
|
|
await test.step('create a dashboard with an insight', async () => {
|
|
await dashboard.createNew()
|
|
await dashboard.addInsightToNewDashboard(insightName)
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
dashboardUrl = page.url()
|
|
})
|
|
|
|
await test.step('select to edit an insight', async () => {
|
|
await dashboard.openFirstTileMenu()
|
|
await dashboard.selectTileMenuOption('Edit')
|
|
await expect(page).toHaveURL(/\/insights\/.+\/edit(?:\?|$)/)
|
|
})
|
|
|
|
await test.step('edit the insight name and save', async () => {
|
|
// Wait for the insight to fully load before editing — the save button
|
|
// shows "No changes" once the API response has been applied to the store.
|
|
// Without this, a late-arriving loadInsightSuccess can overwrite the
|
|
// local name change and leave the save button permanently disabled.
|
|
await expect(insight.saveButton).toContainText('No changes')
|
|
await insight.editName(updatedName)
|
|
await expect(insight.topBarName).toContainText(updatedName)
|
|
await expect(insight.saveButton).toBeEnabled()
|
|
await insight.save()
|
|
})
|
|
|
|
await test.step('navigate to dashboard and verify the updated insight', async () => {
|
|
await page.goto(dashboardUrl, { waitUntil: 'domcontentloaded' })
|
|
await expect(page).toHaveURL(/\/dashboard\//)
|
|
await expect(dashboard.insightCards.first()).toBeVisible()
|
|
const updatedCard = await dashboard.findCardByTitle(updatedName)
|
|
await expect(updatedCard.locator('[data-attr="insight-card-title"]')).toContainText(updatedName)
|
|
})
|
|
})
|
|
|
|
test('Add insight to new dashboard and view it there', async ({ page }) => {
|
|
const insight = new InsightPage(page)
|
|
const dashboard = new DashboardPage(page)
|
|
const insightName = randomString('add-to-dash')
|
|
|
|
await test.step('create and save a Trends insight', async () => {
|
|
await insight.goToNewTrends()
|
|
await insight.trends.waitForChart()
|
|
await insight.editName(insightName)
|
|
await insight.save()
|
|
await expect(insight.editButton).toBeVisible()
|
|
})
|
|
|
|
await test.step('add insight to a new dashboard', async () => {
|
|
await dashboard.addToNewDashboardFromInsightPage()
|
|
})
|
|
|
|
await test.step('verify insight is visible on the new dashboard', async () => {
|
|
await expect(page).toHaveURL(/\/dashboard\//)
|
|
const card = dashboard.insightCards.filter({ hasText: insightName })
|
|
await expect(card).toBeVisible()
|
|
await expect(card.locator('canvas[role="img"]')).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test('Can duplicate, rename, and remove dashboard tiles', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const newTileName = randomString('tile-name')
|
|
|
|
await test.step('create a dashboard with an insight', async () => {
|
|
await dashboard.createNew()
|
|
await dashboard.addInsightToNewDashboard()
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
})
|
|
|
|
await test.step('duplicate the tile', async () => {
|
|
const titleLocator = dashboard.insightCards.first().getByTestId('insight-card-title')
|
|
await expect(titleLocator).not.toContainText('Loading')
|
|
const title = await titleLocator.textContent()
|
|
await dashboard.openFirstTileMenu()
|
|
await dashboard.selectTileMenuOption('Duplicate')
|
|
|
|
const duplicateTile = page.getByText(`${title} (Copy)`)
|
|
await duplicateTile.scrollIntoViewIfNeeded()
|
|
await expect(duplicateTile).toBeVisible()
|
|
})
|
|
|
|
await test.step('rename the first tile', async () => {
|
|
await dashboard.openFirstTileMenu()
|
|
await dashboard.selectTileMenuOption('Rename')
|
|
|
|
const renameModal = page.locator('.Modal').filter({ has: page.getByTestId('insight-name') })
|
|
await renameModal.getByTestId('insight-name').fill(newTileName)
|
|
await renameModal.getByText('Submit').click()
|
|
|
|
await expect(dashboard.insightCards.first().getByText(newTileName)).toBeVisible()
|
|
})
|
|
|
|
await test.step('remove the first tile', async () => {
|
|
await dashboard.openFirstTileMenu()
|
|
await dashboard.selectTileMenuOption('Remove from dashboard')
|
|
|
|
await expect(dashboard.insightCards.first().getByText(newTileName)).not.toBeVisible()
|
|
})
|
|
})
|
|
|
|
test('Creating a SQL insight with a variable and overriding it on a dashboard', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const insight = new InsightPage(page)
|
|
|
|
await test.step('open SQL editor and create a Number variable', async () => {
|
|
await page.goto('/sql')
|
|
await page.getByRole('button', { name: 'Variables' }).click()
|
|
await page.getByRole('menuitem', { name: 'New variable' }).click()
|
|
await page.getByRole('menuitem', { name: 'Number', exact: true }).click()
|
|
|
|
const dialog = page.locator('.Modal').filter({ hasText: 'New Number variable' })
|
|
await expect(dialog).toBeVisible()
|
|
await dialog.getByRole('textbox', { name: 'Name' }).fill('Test Number')
|
|
await dialog.getByRole('spinbutton').fill('5')
|
|
|
|
// Saving the variable kicks off a reload of the insightVariables list, and the
|
|
// editor can only substitute {variables.test_number} once that list has loaded.
|
|
// Running the query before the reload returns sends the placeholder unresolved,
|
|
// the backend errors with "Global variable not found: variables", and
|
|
// "Save as insight" never enables. Wait for the reload before using the variable.
|
|
const variablesReloaded = page.waitForResponse(
|
|
(response) =>
|
|
response.url().includes('/insight_variables') &&
|
|
response.request().method() === 'GET' &&
|
|
response.ok()
|
|
)
|
|
await dialog.getByRole('button', { name: 'Save' }).click()
|
|
await expect(dialog).not.toBeVisible()
|
|
await variablesReloaded
|
|
})
|
|
|
|
await test.step('write query, run, and save as insight', async () => {
|
|
const editor = page.locator('.monaco-editor').first()
|
|
await editor.click()
|
|
await page.keyboard.press('ControlOrMeta+a')
|
|
// insertText() inserts the whole string at once, so Monaco's bracket
|
|
// auto-close and autocomplete don't intercept the `{`/`}` mid-type and
|
|
// produce a malformed query that leaves "Save as insight" disabled.
|
|
await page.keyboard.insertText('SELECT {variables.test_number}')
|
|
await page.keyboard.press('Escape')
|
|
|
|
// The editor attaches the variable to the query through a short debounce, so a
|
|
// fast first Run can execute before {variables.test_number} is substituted —
|
|
// the query then errors with "Global variable not found: variables" and leaves
|
|
// "Save as insight" disabled. Re-run until the variable resolves and the query
|
|
// succeeds (a successful run is the only thing that enables "Save as insight").
|
|
const saveAsInsight = page.getByRole('button', { name: 'Save as insight' })
|
|
await expect(async () => {
|
|
await page.getByRole('button', { name: 'Run' }).click()
|
|
await expect(page.locator('[data-attr=sql-editor-output-pane-empty-state]')).not.toBeVisible()
|
|
await expect(saveAsInsight).toBeEnabled({ timeout: 15000 })
|
|
}).toPass({ timeout: 60000 })
|
|
await saveAsInsight.click()
|
|
|
|
const saveModal = page.locator('.Modal').filter({ hasText: 'Save as new insight' })
|
|
await expect(saveModal).toBeVisible()
|
|
await saveModal.getByPlaceholder('Please enter the new name').fill('Variable test insight')
|
|
await saveModal.getByRole('button', { name: 'Submit' }).click()
|
|
|
|
// After save, the SQL editor redirects to the insight view page
|
|
await expect(page).toHaveURL(/\/insights\//)
|
|
await expect(insight.editButton).toBeVisible()
|
|
})
|
|
|
|
await test.step('add the insight to a new dashboard', async () => {
|
|
await dashboard.addToNewDashboardFromInsightPage()
|
|
})
|
|
|
|
await test.step('verify the variable control shows the default value', async () => {
|
|
await expect(page).toHaveURL(/\/dashboard\//)
|
|
await dashboard.closeInfoPanel()
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
await expect(dashboard.variableButtons.first()).toContainText('5')
|
|
})
|
|
|
|
await test.step('change the variable value via the UI', async () => {
|
|
await dashboard.setVariable('Test Number', 99)
|
|
})
|
|
|
|
await test.step('verify the variable button reflects the new value', async () => {
|
|
await expect(dashboard.variableButtons.first()).toContainText('99')
|
|
})
|
|
})
|
|
|
|
test('Deleting a dashboard navigates to the dashboards list', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const dashboardName = randomString('dash-delete')
|
|
|
|
await test.step('create a dashboard', async () => {
|
|
await dashboard.createNew(dashboardName)
|
|
await expect(page).toHaveURL(/\/dashboard\/\d+/)
|
|
})
|
|
|
|
await test.step('delete the dashboard', async () => {
|
|
await dashboard.deleteDashboard()
|
|
})
|
|
|
|
await test.step('verify navigation to dashboards list (not "Not found")', async () => {
|
|
await expect(page).toHaveURL(/\/dashboard(#panel=[a-z]+)?$/)
|
|
await expect(page.getByText('Not found')).not.toBeVisible()
|
|
// Reload so the list is rebuilt from the server, which excludes deleted
|
|
// dashboards. The in-memory dashboardsModel can keep a stale row visible
|
|
// (the list selector doesn't filter `deleted`, and an in-flight load can
|
|
// re-merge it after delayedDeleteDashboard prunes it), so an unscoped or
|
|
// even table-scoped assertion against live state is racy. Scope to the
|
|
// table to exclude the "deleted" toast, which also contains the name.
|
|
await page.reload()
|
|
await expect(page.getByTestId('dashboards-table')).toBeVisible()
|
|
await expect(page.getByTestId('dashboards-table').getByText(dashboardName)).not.toBeVisible()
|
|
})
|
|
})
|
|
|
|
test('Deleting an insight from dashboard redirects back', async ({ page }) => {
|
|
test.setTimeout(120_000)
|
|
const dashboard = new DashboardPage(page)
|
|
const insight = new InsightPage(page)
|
|
const insightName = randomString('dash-trends')
|
|
|
|
await test.step('create a saved Trends insight', async () => {
|
|
await createSavedTrendsInsight(page, insightName, workspace!)
|
|
})
|
|
|
|
await test.step('create a dashboard with an insight', async () => {
|
|
await dashboard.createNew()
|
|
await dashboard.addInsightToNewDashboard(insightName)
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
})
|
|
|
|
await test.step('open the insight from the dashboard', async () => {
|
|
await dashboard.openFirstTileMenu()
|
|
await dashboard.selectTileMenuOption('Edit')
|
|
await expect(page).toHaveURL(/\/insights\/.+\/edit(?:\?|$)/)
|
|
// Wait for the insight to fully load before interacting with the
|
|
// side panel — InsightPanelDangerZone only renders the delete
|
|
// button once the insight data is available in the store.
|
|
await expect(insight.saveButton).toContainText('No changes')
|
|
})
|
|
|
|
await test.step('open the info panel and click delete', async () => {
|
|
await insight.openInfoPanel()
|
|
await insight.clickDeleteInsight()
|
|
await expect(page.locator('.Modal').filter({ hasText: 'Delete insight?' })).toBeVisible()
|
|
})
|
|
|
|
await test.step('cancel the dialog and verify we stay on the insight', async () => {
|
|
await insight.cancelDeleteDialog()
|
|
await expect(page).toHaveURL(/edit/)
|
|
})
|
|
|
|
await test.step('click delete again and confirm', async () => {
|
|
await insight.clickDeleteInsight()
|
|
await insight.confirmDeleteDialog()
|
|
})
|
|
|
|
await test.step('verify redirect to the dashboard with no insight cards', async () => {
|
|
await expect(page).toHaveURL(/\/dashboard\//)
|
|
await expect(dashboard.insightCards).toHaveCount(0)
|
|
})
|
|
})
|
|
|
|
test('Creating a dashboard from a template populates tiles', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
|
|
await test.step('create a dashboard from a template', async () => {
|
|
await dashboard.createFromTemplate()
|
|
})
|
|
|
|
await test.step('verify the template dashboard has tiles', async () => {
|
|
await expect(dashboard.insightCards.first()).toBeVisible({ timeout: 30000 })
|
|
|
|
const tileCount = await dashboard.insightCards.count()
|
|
expect(tileCount).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
test.describe('Dashboard duplication', () => {
|
|
let workspace: PlaywrightWorkspaceSetupResult | null = null
|
|
|
|
test.beforeAll(async ({ playwrightSetup }) => {
|
|
workspace = await playwrightSetup.createWorkspace({
|
|
use_current_time: true,
|
|
skip_onboarding: true,
|
|
insight_variables: [{ name: 'Test Var', type: 'Number', default_value: 10 }],
|
|
insights: [
|
|
{
|
|
name: 'Variable insight',
|
|
query: {
|
|
kind: 'DataVisualizationNode',
|
|
source: { kind: 'InsightsQLQuery', query: 'SELECT {variables.test_var}' },
|
|
chartSettings: {},
|
|
tableSettings: {},
|
|
},
|
|
variable_indexes: [0],
|
|
},
|
|
],
|
|
dashboards: [
|
|
{
|
|
name: 'Duplication source',
|
|
insight_indexes: [0],
|
|
variable_overrides: { '0': 42 },
|
|
},
|
|
],
|
|
})
|
|
})
|
|
|
|
test.beforeEach(async ({ page, playwrightSetup }) => {
|
|
await playwrightSetup.login(page, workspace!)
|
|
})
|
|
|
|
test('Duplicating a dashboard preserves text cards, date filter, and variables', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const cardText = randomString('card-text')
|
|
const seededDashboardId = workspace!.created_dashboards![0].id
|
|
let sourceDashboardUrl: string
|
|
|
|
await test.step('navigate to seeded dashboard and add a text card', async () => {
|
|
await page.goto(`/project/${workspace!.team_id}/dashboard/${seededDashboardId}`)
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
await dashboard.addTextCard(cardText)
|
|
})
|
|
|
|
await test.step('set date filter', async () => {
|
|
sourceDashboardUrl = page.url()
|
|
await dashboard.setDateFilter('Last 30 days')
|
|
})
|
|
|
|
await test.step('duplicate the dashboard', async () => {
|
|
await dashboard.duplicate()
|
|
await expect(page).not.toHaveURL(sourceDashboardUrl)
|
|
})
|
|
|
|
await test.step('verify duplicated dashboard preserves text card, date filter, and variable override', async () => {
|
|
await expect(dashboard.textCards).toBeVisible()
|
|
await expect(dashboard.textCards).toContainText(cardText)
|
|
await expect(dashboard.dateFilter).toContainText('Last 30 days')
|
|
await expect(dashboard.variableButtons.first()).toContainText('42')
|
|
})
|
|
})
|
|
})
|
|
|
|
test.describe('Dashboard link variable and filter overrides', () => {
|
|
let workspace: PlaywrightWorkspaceSetupResult | null = null
|
|
|
|
test.beforeAll(async ({ playwrightSetup }) => {
|
|
workspace = await playwrightSetup.createWorkspace({
|
|
use_current_time: true,
|
|
skip_onboarding: true,
|
|
insight_variables: [{ name: 'Test Var', type: 'Number', default_value: 10 }],
|
|
insights: [
|
|
{
|
|
name: 'Variable insight',
|
|
query: {
|
|
kind: 'DataVisualizationNode',
|
|
source: { kind: 'InsightsQLQuery', query: 'SELECT {variables.test_var}' },
|
|
chartSettings: {},
|
|
tableSettings: {},
|
|
},
|
|
variable_indexes: [0],
|
|
},
|
|
],
|
|
dashboards: [
|
|
{
|
|
name: 'URL query variables seed',
|
|
insight_indexes: [0],
|
|
variable_overrides: { '0': 42 },
|
|
filters: { date_from: '-30d' },
|
|
},
|
|
],
|
|
})
|
|
})
|
|
|
|
test.beforeEach(async ({ page, playwrightSetup }) => {
|
|
await playwrightSetup.login(page, workspace!)
|
|
})
|
|
|
|
test('Opening a shared dashboard link applies variable and filter overrides from the URL on first load', async ({
|
|
page,
|
|
}) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const seededDashboardId = workspace!.created_dashboards![0].id
|
|
const urlOverride = 77
|
|
const searchParams = new URLSearchParams()
|
|
searchParams.set('query_variables', JSON.stringify({ test_var: urlOverride }))
|
|
searchParams.set('query_filters', JSON.stringify({ date_from: '-7d' }))
|
|
|
|
await page.goto(`/project/${workspace!.team_id}/dashboard/${seededDashboardId}?${searchParams.toString()}`, {
|
|
waitUntil: 'domcontentloaded',
|
|
})
|
|
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
await expect(dashboard.variableButtons.first()).toContainText(String(urlOverride))
|
|
await expect(dashboard.dateFilter).toContainText('Last 7 days')
|
|
await expect(dashboard.overridesBanner).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test.describe('Dashboard compact cards and inline editing', () => {
|
|
let workspace: PlaywrightWorkspaceSetupResult | null = null
|
|
|
|
test.beforeAll(async ({ playwrightSetup }) => {
|
|
workspace = await playwrightSetup.createWorkspace({ use_current_time: true, skip_onboarding: true })
|
|
})
|
|
|
|
test.beforeEach(async ({ page, playwrightSetup }) => {
|
|
await playwrightSetup.login(page, workspace!)
|
|
})
|
|
|
|
test('Edit mode button enters and exits edit mode', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const insightName = randomString('dash-trends')
|
|
|
|
await test.step('create a saved Trends insight', async () => {
|
|
await createSavedTrendsInsight(page, insightName, workspace!)
|
|
})
|
|
|
|
await test.step('create a dashboard with an insight', async () => {
|
|
await dashboard.createNew()
|
|
await dashboard.addInsightToNewDashboard(insightName)
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
})
|
|
|
|
await test.step('enter edit mode via the edit button', async () => {
|
|
await dashboard.enterEditMode()
|
|
})
|
|
|
|
await test.step('save and exit edit mode', async () => {
|
|
await dashboard.saveEditMode()
|
|
})
|
|
})
|
|
|
|
test('Inline editing insight title via compact card popover', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const insightName = randomString('dash-trends')
|
|
const updatedTitle = randomString('inline-title')
|
|
|
|
await test.step('create a saved Trends insight', async () => {
|
|
await createSavedTrendsInsight(page, insightName, workspace!)
|
|
})
|
|
|
|
await test.step('create a dashboard with an insight', async () => {
|
|
await dashboard.createNew()
|
|
await dashboard.addInsightToNewDashboard(insightName)
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
})
|
|
|
|
await test.step('hover over the card to open the popover', async () => {
|
|
await dashboard.hoverFirstCard()
|
|
await expect(dashboard.popoverTitleField).toBeVisible()
|
|
})
|
|
|
|
await test.step('edit the title inline', async () => {
|
|
await dashboard.editPopoverTitle(updatedTitle)
|
|
})
|
|
|
|
await test.step('verify toast and updated title', async () => {
|
|
await expect(page.getByText('Insight updated')).toBeVisible()
|
|
await expect(dashboard.insightCards.first().getByTestId('insight-card-title')).toContainText(updatedTitle)
|
|
})
|
|
|
|
await test.step('reload and verify title persisted', async () => {
|
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
await expect(dashboard.insightCards.first().getByTestId('insight-card-title')).toContainText(updatedTitle)
|
|
})
|
|
})
|
|
|
|
test('Inline editing insight description via compact card popover', async ({ page }) => {
|
|
const dashboard = new DashboardPage(page)
|
|
const insightName = randomString('dash-trends')
|
|
const description = randomString('inline-desc')
|
|
|
|
await test.step('create a saved Trends insight', async () => {
|
|
await createSavedTrendsInsight(page, insightName, workspace!)
|
|
})
|
|
|
|
await test.step('create a dashboard with an insight', async () => {
|
|
await dashboard.createNew()
|
|
await dashboard.addInsightToNewDashboard(insightName)
|
|
await expect(dashboard.insightCards).toBeVisible()
|
|
})
|
|
|
|
await test.step('hover over the card to open the popover', async () => {
|
|
await dashboard.hoverFirstCard()
|
|
await expect(dashboard.popoverDescriptionField).toBeVisible()
|
|
})
|
|
|
|
await test.step('edit the description inline', async () => {
|
|
await dashboard.editPopoverDescription(description)
|
|
})
|
|
|
|
await test.step('verify toast', async () => {
|
|
await expect(page.getByText('Insight updated')).toBeVisible()
|
|
})
|
|
|
|
await test.step('reload and verify description persisted', async () => {
|
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
await dashboard.hoverFirstCard()
|
|
await expect(dashboard.tilePopover.getByText(description)).toBeVisible()
|
|
})
|
|
})
|
|
})
|