mirror of
https://github.com/lnbits/lnbits.git
synced 2026-08-13 12:42:47 +02:00
test: support chat tests (#4102)
This commit is contained in:
parent
7258bd32b4
commit
e165269ed8
4 changed files with 472 additions and 20 deletions
|
|
@ -12,6 +12,7 @@ const INSTALLABLE_EXTENSION_REFRESH_TASK =
|
|||
'refresh_installable_extensions_cache'
|
||||
|
||||
export type ExtensionUnderTest = {
|
||||
configUrl?: string
|
||||
extId: string
|
||||
name: string
|
||||
permissionTexts?: string[]
|
||||
|
|
@ -243,33 +244,64 @@ export async function installExtension(
|
|||
await expect(installButton).toBeVisible({timeout: 120_000})
|
||||
await installButton.click()
|
||||
|
||||
const grantButton = page.getByRole('button', {name: /^grant and install$/i})
|
||||
const permissionsDialog = page
|
||||
.locator('.q-dialog')
|
||||
.filter({hasText: 'Grant extension permissions'})
|
||||
.last()
|
||||
let hasPermissionsDialog = false
|
||||
try {
|
||||
await expect(page.getByText('Grant extension permissions')).toBeVisible({
|
||||
await expect(permissionsDialog).toBeVisible({
|
||||
timeout: 10_000
|
||||
})
|
||||
hasPermissionsDialog = true
|
||||
} catch (_error) {}
|
||||
|
||||
if (hasPermissionsDialog) {
|
||||
for (const permissionText of extension.permissionTexts ?? []) {
|
||||
await expect(page.getByText(permissionText).first()).toBeVisible({
|
||||
await expect(
|
||||
permissionsDialog.getByText(permissionText).first()
|
||||
).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
}
|
||||
const grantButton = permissionsDialog.getByRole('button', {
|
||||
name: /^grant and install$/i
|
||||
})
|
||||
await expect(grantButton).toBeEnabled({timeout: 60_000})
|
||||
await grantButton.click()
|
||||
} catch (_error) {}
|
||||
}
|
||||
|
||||
await waitForInstalledExtension(page, extension.extId)
|
||||
const installed = await extensionState(page, extension.extId)
|
||||
const grantedPermissionIds = new Set(
|
||||
Array.isArray(installed?.permissions)
|
||||
? installed.permissions.filter(isRecord).map(permission => permission.id)
|
||||
: []
|
||||
)
|
||||
const latestConfig = await latestReleaseConfig(release)
|
||||
const permissions = Array.isArray(latestConfig.permissions)
|
||||
? latestConfig.permissions.filter(isRecord)
|
||||
: []
|
||||
let installed = await extensionState(page, extension.extId)
|
||||
let grantedPermissionIds = installedPermissionIds(installed)
|
||||
const missingPermissionIds = permissions
|
||||
.map(permission => permission.id)
|
||||
.filter(permissionId => !grantedPermissionIds.has(permissionId))
|
||||
|
||||
if (extension.configUrl && missingPermissionIds.length) {
|
||||
const response = await page
|
||||
.context()
|
||||
.request.put(
|
||||
`/api/v1/extension/${encodeURIComponent(extension.extId)}/permissions`,
|
||||
{data: {permissions}}
|
||||
)
|
||||
expect(
|
||||
response.ok(),
|
||||
`Could not grant local fixture permissions: ${await response.text()}`
|
||||
).toBe(true)
|
||||
installed = await extensionState(page, extension.extId)
|
||||
grantedPermissionIds = installedPermissionIds(installed)
|
||||
}
|
||||
|
||||
for (const permission of permissions) {
|
||||
expect(grantedPermissionIds.has(permission.id)).toBe(true)
|
||||
expect(
|
||||
grantedPermissionIds.has(permission.id),
|
||||
`Missing extension permission: ${String(permission.id)}`
|
||||
).toBe(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -277,18 +309,24 @@ export async function enableExtension(
|
|||
page: Page,
|
||||
extension: ExtensionUnderTest
|
||||
): Promise<void> {
|
||||
await page.goto('/extensions')
|
||||
await selectExtensionsTab(page, 'Installed')
|
||||
await filterExtensions(page, extension.name)
|
||||
if (await userExtensionEnabled(page, extension.extId)) return
|
||||
|
||||
await page.goto(`/extensions#${encodeURIComponent(extension.extId)}`)
|
||||
await dismissDisclaimer(page)
|
||||
const extensionCard = extensionCardFor(page, extension)
|
||||
await expect(extensionCard).toBeVisible({timeout: 120_000})
|
||||
const enableButton = extensionCard.getByRole('button', {name: /^enable$/i})
|
||||
if (await enableButton.isVisible()) {
|
||||
await enableButton.click()
|
||||
await expect(page.getByText('Extension enabled!')).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
}
|
||||
await expect(enableButton).toBeVisible({timeout: 60_000})
|
||||
await enableButton.click()
|
||||
await expect(page.getByText('Extension enabled!')).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
await waitForResult(
|
||||
`${extension.extId} extension to be enabled for the user`,
|
||||
async () =>
|
||||
(await userExtensionEnabled(page, extension.extId)) ? true : null,
|
||||
{timeout: 60_000, interval: 1_000}
|
||||
)
|
||||
await expect(extensionCard.getByRole('link', {name: /^open$/i})).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
|
|
@ -380,6 +418,21 @@ export async function extensionState(
|
|||
)
|
||||
}
|
||||
|
||||
async function userExtensionEnabled(
|
||||
page: Page,
|
||||
extensionId: string
|
||||
): Promise<boolean> {
|
||||
const extensions = await browserJson(page, 'GET', '/api/v1/extension')
|
||||
if (!Array.isArray(extensions)) {
|
||||
throw new Error(
|
||||
`User extensions response is not a list: ${JSON.stringify(extensions)}`
|
||||
)
|
||||
}
|
||||
return extensions
|
||||
.filter(isRecord)
|
||||
.some(extension => extension.code === extensionId)
|
||||
}
|
||||
|
||||
export async function grantBackgroundPaymentPermission(
|
||||
page: Page,
|
||||
extensionId: string,
|
||||
|
|
@ -533,6 +586,16 @@ function latestReleaseFor(
|
|||
return release
|
||||
}
|
||||
|
||||
function installedPermissionIds(
|
||||
extension: Record<string, unknown> | null
|
||||
): Set<unknown> {
|
||||
return new Set(
|
||||
Array.isArray(extension?.permissions)
|
||||
? extension.permissions.filter(isRecord).map(permission => permission.id)
|
||||
: []
|
||||
)
|
||||
}
|
||||
|
||||
async function latestReleaseConfig(
|
||||
release: Record<string, unknown>
|
||||
): Promise<Record<string, unknown>> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
import type {ExtensionUnderTest} from './extension-helpers'
|
||||
|
||||
const extensionFixtureUrl =
|
||||
process.env.LNBITS_E2E_EXTENSION_FIXTURE_URL ?? 'http://127.0.0.1:5010'
|
||||
|
||||
export const SUPPORTCHAT: ExtensionUnderTest = {
|
||||
extId: 'supportchat',
|
||||
name: 'Support Chat',
|
||||
configUrl: `${extensionFixtureUrl}/config.json`,
|
||||
permissionTexts: [
|
||||
'Read public extension storage',
|
||||
'Append public extension storage',
|
||||
'Use extension websockets'
|
||||
]
|
||||
}
|
||||
|
||||
export const TIPS: ExtensionUnderTest = {
|
||||
extId: 'tips',
|
||||
name: 'Tips',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
const childProcess = require('node:child_process')
|
||||
const crypto = require('node:crypto')
|
||||
const fs = require('node:fs')
|
||||
const http = require('node:http')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
|
||||
|
|
@ -14,10 +16,41 @@ const dataDir =
|
|||
fs.mkdtempSync(path.join(os.tmpdir(), 'lnbits-e2e-'))
|
||||
const logDir = path.join(rootDir, 'test-reports', 'test-results')
|
||||
const logFile = path.join(logDir, 'lnbits-e2e-server.log')
|
||||
const extensionFixtureUrl = new URL(
|
||||
process.env.LNBITS_E2E_EXTENSION_FIXTURE_URL ?? 'http://127.0.0.1:5010'
|
||||
)
|
||||
|
||||
fs.mkdirSync(dataDir, {recursive: true})
|
||||
fs.mkdirSync(logDir, {recursive: true})
|
||||
|
||||
const supportchatFixture = createSupportchatFixture(extensionFixtureUrl)
|
||||
const wasmExtensionManifests = [
|
||||
...(supportchatFixture
|
||||
? [new URL('/manifest.json', extensionFixtureUrl).toString()]
|
||||
: []),
|
||||
'https://raw.githubusercontent.com/lnbits/lnbits-extensions-wasm/refs/heads/main/extensions.json'
|
||||
]
|
||||
const extensionFixtureServer = supportchatFixture
|
||||
? http.createServer((request, response) => {
|
||||
const pathname = new URL(request.url ?? '/', extensionFixtureUrl).pathname
|
||||
const fixture = supportchatFixture.responses[pathname]
|
||||
if (!fixture) {
|
||||
response.writeHead(404, {'Content-Type': 'text/plain; charset=utf-8'})
|
||||
response.end('Not found')
|
||||
return
|
||||
}
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': 'no-store',
|
||||
'Content-Type': fixture.contentType
|
||||
})
|
||||
response.end(fixture.body)
|
||||
})
|
||||
: null
|
||||
extensionFixtureServer?.listen(
|
||||
Number(extensionFixtureUrl.port || 80),
|
||||
extensionFixtureUrl.hostname
|
||||
)
|
||||
|
||||
const log = fs.openSync(logFile, 'a')
|
||||
const server = childProcess.spawn(
|
||||
'uv',
|
||||
|
|
@ -44,6 +77,7 @@ const server = childProcess.spawn(
|
|||
LNBITS_DATA_FOLDER: dataDir,
|
||||
LNBITS_ENABLE_LOG_TO_FILE: 'false',
|
||||
LNBITS_EXTENSIONS_PATH: dataDir,
|
||||
LNBITS_WASM_EXTENSIONS_MANIFESTS: JSON.stringify(wasmExtensionManifests),
|
||||
PORT: port,
|
||||
PYTHONUNBUFFERED: '1'
|
||||
},
|
||||
|
|
@ -56,6 +90,7 @@ let shuttingDown = false
|
|||
const shutdown = signal => {
|
||||
if (shuttingDown) return
|
||||
shuttingDown = true
|
||||
extensionFixtureServer?.close()
|
||||
|
||||
if (server.pid && server.exitCode === null) {
|
||||
try {
|
||||
|
|
@ -77,8 +112,80 @@ process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|||
process.on('SIGINT', () => shutdown('SIGINT'))
|
||||
|
||||
server.on('exit', (code, signal) => {
|
||||
extensionFixtureServer?.close()
|
||||
fs.closeSync(log)
|
||||
if (!shuttingDown) {
|
||||
process.exit(code ?? (signal ? 1 : 0))
|
||||
}
|
||||
})
|
||||
|
||||
function createSupportchatFixture(fixtureUrl) {
|
||||
const sourceDir =
|
||||
process.env.LNBITS_E2E_SUPPORTCHAT_DIR ??
|
||||
path.join(rootDir, 'data', 'extensions', 'supportchat')
|
||||
if (!fs.existsSync(path.join(sourceDir, 'config.json'))) return null
|
||||
const config = JSON.parse(
|
||||
fs.readFileSync(path.join(sourceDir, 'config.json'), 'utf8')
|
||||
)
|
||||
const fixtureRoot = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'lnbits-supportchat-e2e-')
|
||||
)
|
||||
const archiveRootName = `supportchat-${config.version}`
|
||||
const archiveRoot = path.join(fixtureRoot, archiveRootName)
|
||||
fs.mkdirSync(archiveRoot, {recursive: true})
|
||||
|
||||
for (const name of ['config.json', 'static', 'storage', 'ui', 'wasm']) {
|
||||
fs.cpSync(path.join(sourceDir, name), path.join(archiveRoot, name), {
|
||||
recursive: true
|
||||
})
|
||||
}
|
||||
|
||||
const archivePath = path.join(fixtureRoot, 'supportchat.zip')
|
||||
const zipped = childProcess.spawnSync(
|
||||
'zip',
|
||||
['-q', '-r', archivePath, archiveRootName],
|
||||
{
|
||||
cwd: fixtureRoot,
|
||||
encoding: 'utf8'
|
||||
}
|
||||
)
|
||||
if (zipped.status !== 0) {
|
||||
throw new Error(`Could not create supportchat fixture: ${zipped.stderr}`)
|
||||
}
|
||||
const archive = fs.readFileSync(archivePath)
|
||||
const hash = crypto.createHash('sha256').update(archive).digest('hex')
|
||||
const archiveUrl = new URL('/supportchat.zip', fixtureUrl).toString()
|
||||
const configUrl = new URL('/config.json', fixtureUrl).toString()
|
||||
const manifest = {
|
||||
extensions: [
|
||||
{
|
||||
id: 'supportchat',
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
archive: archiveUrl,
|
||||
hash,
|
||||
repo: 'local-e2e',
|
||||
short_description: config.short_description,
|
||||
min_lnbits_version: config.min_lnbits_version,
|
||||
details_link: configUrl
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return {
|
||||
responses: {
|
||||
'/config.json': {
|
||||
body: Buffer.from(JSON.stringify(config)),
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
},
|
||||
'/manifest.json': {
|
||||
body: Buffer.from(JSON.stringify(manifest)),
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
},
|
||||
'/supportchat.zip': {
|
||||
body: archive,
|
||||
contentType: 'application/zip'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
268
tests/e2e/supportchat.spec.ts
Normal file
268
tests/e2e/supportchat.spec.ts
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
import {existsSync} from 'node:fs'
|
||||
import {join, resolve} from 'node:path'
|
||||
|
||||
import {
|
||||
expect,
|
||||
installDetailedScreenshots,
|
||||
isRecord,
|
||||
randomHex,
|
||||
test
|
||||
} from './fixtures'
|
||||
import {
|
||||
extensionApi,
|
||||
extensionFrame,
|
||||
installAndEnableExtension,
|
||||
login
|
||||
} from './extension-helpers'
|
||||
import {SUPPORTCHAT} from './extensions'
|
||||
|
||||
const supportchatDir =
|
||||
process.env.LNBITS_E2E_SUPPORTCHAT_DIR ??
|
||||
resolve('data/extensions/supportchat')
|
||||
|
||||
test.skip(
|
||||
!existsSync(join(supportchatDir, 'config.json')),
|
||||
'Support Chat E2E requires LNBITS_E2E_SUPPORTCHAT_DIR or data/extensions/supportchat.'
|
||||
)
|
||||
|
||||
test('install Support Chat and run the visitor-to-agent support workflow', async ({
|
||||
page,
|
||||
browser,
|
||||
lnbitsServer
|
||||
}, testInfo) => {
|
||||
await login(page, lnbitsServer)
|
||||
await installAndEnableExtension(page, SUPPORTCHAT)
|
||||
|
||||
await page.goto('/ext/supportchat')
|
||||
let adminFrame = await extensionFrame(page, 'Support Chat')
|
||||
await expect(
|
||||
adminFrame.getByRole('heading', {name: 'Support Chat'})
|
||||
).toBeVisible({timeout: 60_000})
|
||||
|
||||
const inboxName = `Playwright Support ${randomHex()}`
|
||||
const inboxForm = adminFrame.locator('#inbox-form')
|
||||
const createInboxButton = inboxForm.getByRole('button', {
|
||||
name: 'Create inbox'
|
||||
})
|
||||
await expect(createInboxButton).toBeEnabled()
|
||||
await inboxForm.locator('[name="name"]').fill(inboxName)
|
||||
await inboxForm
|
||||
.locator('[name="welcomeMessage"]')
|
||||
.fill('How can the Playwright support team help?')
|
||||
await inboxForm.locator('[name="launcherText"]').fill('Ask Playwright')
|
||||
await inboxForm
|
||||
.locator('[name="offlineMessage"]')
|
||||
.fill('Playwright support is offline; leave a message.')
|
||||
await inboxForm.locator('[name="officeHoursEnabled"]').check()
|
||||
await inboxForm.locator('[name="officeHoursStart"]').fill('0')
|
||||
await inboxForm.locator('[name="officeHoursEnd"]').fill('0')
|
||||
await createInboxButton.click()
|
||||
await expect(adminFrame.getByText(inboxName).first()).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
|
||||
const inbox = await supportInbox(page, inboxName)
|
||||
const publicPath = `/ext/supportchat/i/${encodeURIComponent(String(inbox.id))}`
|
||||
const visitorContext = await browser.newContext({
|
||||
baseURL: lnbitsServer.baseUrl,
|
||||
viewport: {width: 430, height: 820}
|
||||
})
|
||||
const visitorPage = await visitorContext.newPage()
|
||||
visitorPage.setDefaultTimeout(60_000)
|
||||
const visitorRecorder = await installDetailedScreenshots(
|
||||
visitorPage,
|
||||
testInfo
|
||||
)
|
||||
|
||||
try {
|
||||
await visitorPage.goto(publicPath)
|
||||
let visitorFrame = await extensionFrame(visitorPage, 'Support Chat')
|
||||
await expect(visitorFrame.getByText(inboxName)).toBeVisible()
|
||||
await expect(
|
||||
visitorFrame.getByText('Playwright support is offline; leave a message.')
|
||||
).toBeVisible()
|
||||
|
||||
const startForm = visitorFrame.locator('#start-box')
|
||||
await startForm.locator('[name="name"]').fill('Alice Visitor')
|
||||
await startForm
|
||||
.locator('[name="email"]')
|
||||
.fill('alice.playwright@example.com')
|
||||
await startForm.locator('[name="subject"]').fill('Checkout is stuck')
|
||||
await startForm
|
||||
.locator('[name="body"]')
|
||||
.fill('The checkout spinner never finishes.')
|
||||
await startForm.getByTestId('start-conversation').click()
|
||||
await expect(visitorPage).toHaveURL(/\/ext\/supportchat\/c\/[a-f0-9]+$/i, {
|
||||
timeout: 60_000
|
||||
})
|
||||
visitorFrame = await extensionFrame(visitorPage, 'Support Chat')
|
||||
await expect(
|
||||
visitorFrame.getByText('The checkout spinner never finishes.')
|
||||
).toBeVisible()
|
||||
|
||||
await page.goto('/ext/supportchat')
|
||||
adminFrame = await extensionFrame(page, 'Support Chat')
|
||||
await expect(adminFrame.getByText('Checkout is stuck')).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
await expect(adminFrame.getByTestId('unread-count')).toHaveText('1')
|
||||
await adminFrame.getByText('Checkout is stuck').click()
|
||||
await expect(
|
||||
adminFrame.getByText('The checkout spinner never finishes.')
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
adminFrame
|
||||
.locator('.sc-message--theirs')
|
||||
.filter({hasText: 'The checkout spinner never finishes.'})
|
||||
).toHaveCSS('justify-content', 'flex-end')
|
||||
|
||||
const cannedForm = adminFrame.locator('#canned-reply-form')
|
||||
await cannedForm.locator('[name="title"]').fill('Investigating')
|
||||
await cannedForm
|
||||
.locator('[name="body"]')
|
||||
.fill('Thanks — we are investigating this now.')
|
||||
await cannedForm.getByRole('button', {name: 'Add canned reply'}).click()
|
||||
await expect(
|
||||
adminFrame.getByRole('button', {name: 'Investigating', exact: true})
|
||||
).toBeVisible()
|
||||
|
||||
await adminFrame.getByTestId('conversation-status').selectOption('pending')
|
||||
await adminFrame.getByTestId('conversation-priority').selectOption('urgent')
|
||||
await adminFrame.getByTestId('conversation-tags').fill('checkout, browser')
|
||||
const ticketUpdated = page.waitForResponse(
|
||||
response =>
|
||||
response.request().method() === 'PUT' &&
|
||||
response.url().includes('/api/v1/ext/supportchat/conversations/')
|
||||
)
|
||||
await adminFrame.getByRole('button', {name: 'Save ticket'}).click()
|
||||
await ticketUpdated
|
||||
await expect(adminFrame.locator('main.sc-shell')).toHaveAttribute(
|
||||
'data-loading',
|
||||
''
|
||||
)
|
||||
await expect(adminFrame.getByTestId('conversation-status')).toHaveValue(
|
||||
'pending'
|
||||
)
|
||||
await expect(adminFrame.getByTestId('conversation-priority')).toHaveValue(
|
||||
'urgent'
|
||||
)
|
||||
await expect(adminFrame.getByTestId('conversation-tags')).toHaveValue(
|
||||
'checkout, browser'
|
||||
)
|
||||
|
||||
await adminFrame
|
||||
.getByTestId('internal-note')
|
||||
.fill('Only agents should see this diagnostic note.')
|
||||
await adminFrame.getByRole('button', {name: 'Add internal note'}).click()
|
||||
await expect(
|
||||
adminFrame.getByText('Only agents should see this diagnostic note.')
|
||||
).toBeVisible()
|
||||
|
||||
await adminFrame
|
||||
.getByRole('button', {name: 'Investigating', exact: true})
|
||||
.click()
|
||||
await expect(adminFrame.getByTestId('agent-reply')).toHaveValue(
|
||||
'Thanks — we are investigating this now.'
|
||||
)
|
||||
await adminFrame.getByRole('button', {name: 'Send'}).click()
|
||||
await expect(
|
||||
visitorFrame.getByText('Thanks — we are investigating this now.')
|
||||
).toBeVisible({timeout: 60_000})
|
||||
await expect(
|
||||
adminFrame
|
||||
.locator('.sc-message--mine')
|
||||
.filter({hasText: 'Thanks — we are investigating this now.'})
|
||||
).toHaveCSS('justify-content', 'flex-start')
|
||||
await expect(
|
||||
visitorFrame
|
||||
.locator('.sc-message--theirs')
|
||||
.filter({hasText: 'Thanks — we are investigating this now.'})
|
||||
).toHaveCSS('justify-content', 'flex-end')
|
||||
await expect(
|
||||
visitorFrame.getByText('Only agents should see this diagnostic note.')
|
||||
).toHaveCount(0)
|
||||
|
||||
await visitorPage.waitForTimeout(1_100)
|
||||
await visitorFrame
|
||||
.locator('#message-box [name="body"]')
|
||||
.fill('It also happens in a private window.')
|
||||
await visitorFrame.getByTestId('visitor-send').click()
|
||||
await expect(
|
||||
adminFrame.getByText('It also happens in a private window.')
|
||||
).toBeVisible({timeout: 60_000})
|
||||
await expect(adminFrame.getByTestId('unread-count')).toHaveText('1')
|
||||
|
||||
const ticketResolved = page.waitForResponse(
|
||||
response =>
|
||||
response.request().method() === 'POST' &&
|
||||
response.url().includes('/api/v1/ext/supportchat/conversations/') &&
|
||||
response.url().endsWith('/resolve')
|
||||
)
|
||||
await adminFrame.getByRole('button', {name: 'Resolve'}).click()
|
||||
const ticketResolvedResponse = await ticketResolved
|
||||
expect(
|
||||
ticketResolvedResponse.ok(),
|
||||
`Could not resolve ticket: ${await ticketResolvedResponse.text()}`
|
||||
).toBe(true)
|
||||
await expect(visitorFrame.getByText('resolved').first()).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
await expect(adminFrame.locator('main.sc-shell')).toHaveAttribute(
|
||||
'data-loading',
|
||||
''
|
||||
)
|
||||
await expect(adminFrame.getByTestId('conversation-status')).toHaveValue(
|
||||
'resolved'
|
||||
)
|
||||
await adminFrame.getByTestId('conversation-status').selectOption('open')
|
||||
const ticketReopened = page.waitForResponse(
|
||||
response =>
|
||||
response.request().method() === 'PUT' &&
|
||||
response.url().includes('/api/v1/ext/supportchat/conversations/')
|
||||
)
|
||||
await adminFrame.getByRole('button', {name: 'Save ticket'}).click()
|
||||
const ticketReopenedResponse = await ticketReopened
|
||||
expect(
|
||||
ticketReopenedResponse.ok(),
|
||||
`Could not reopen ticket: ${await ticketReopenedResponse.text()}`
|
||||
).toBe(true)
|
||||
await expect(adminFrame.locator('main.sc-shell')).toHaveAttribute(
|
||||
'data-loading',
|
||||
''
|
||||
)
|
||||
await visitorPage.reload()
|
||||
visitorFrame = await extensionFrame(visitorPage, 'Support Chat')
|
||||
await expect(visitorFrame.getByText('open').first()).toBeVisible({
|
||||
timeout: 60_000
|
||||
})
|
||||
|
||||
await visitorPage.goto(publicPath)
|
||||
visitorFrame = await extensionFrame(visitorPage, 'Support Chat')
|
||||
await expect(
|
||||
visitorFrame.getByText('Thanks — we are investigating this now.')
|
||||
).toBeVisible({timeout: 60_000})
|
||||
} finally {
|
||||
await visitorRecorder.finish()
|
||||
await visitorContext.close()
|
||||
}
|
||||
})
|
||||
|
||||
async function supportInbox(
|
||||
page: Parameters<typeof extensionApi>[0],
|
||||
inboxName: string
|
||||
): Promise<Record<string, unknown>> {
|
||||
const response = await extensionApi(
|
||||
page,
|
||||
SUPPORTCHAT.extId,
|
||||
'GET',
|
||||
'/inboxes?rowsPerPage=100'
|
||||
)
|
||||
const inboxes = Array.isArray(response.inboxes)
|
||||
? response.inboxes.filter(isRecord)
|
||||
: []
|
||||
const inbox = inboxes.find(item => item.name === inboxName)
|
||||
if (!inbox) {
|
||||
throw new Error(`Support inbox not found: ${JSON.stringify(response)}`)
|
||||
}
|
||||
return inbox
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue