diff --git a/web/e2e/model-pricing.spec.ts b/web/e2e/model-pricing.spec.ts new file mode 100644 index 0000000..515f13e --- /dev/null +++ b/web/e2e/model-pricing.spec.ts @@ -0,0 +1,107 @@ +import type { APIRequestContext, Locator, Page } from '@playwright/test'; +import { expect, rootStorageStatePath, test } from './fixtures/cluster'; + +type ModelPricingConfig = { + success: boolean; + data: { + schema_version: number; + scope: string; + dimensions: Array<{ key: string }>; + table: unknown[]; + }; +}; + +const modelName = () => + `e2e-video-pricing-model-${Date.now()}-${Math.floor(Math.random() * 1000)}`; + +async function fillControl(locator: Locator, value: string) { + const input = locator.locator('input'); + if ((await input.count()) > 0) { + await input.first().fill(value); + return; + } + await locator.fill(value); +} + +async function fillByTestId(page: Page, testId: string, value: string) { + await fillControl(page.getByTestId(testId), value); +} + +async function fetchModelPricing( + request: APIRequestContext, + model: string, +): Promise { + const response = await request.get( + `/api/option/model_pricing/${encodeURIComponent(model)}`, + ); + expect(response.ok()).toBeTruthy(); + return (await response.json()) as ModelPricingConfig; +} + +test.describe.serial('Model Pricing E2E', () => { + test.use({ storageState: rootStorageStatePath }); + + test('root can save and reload a generated model pricing config', async ({ + page, + request, + }) => { + const model = modelName(); + + await page.goto('/console/setting?tab=ratio'); + await page.getByTestId('model-pricing-tab').click(); + await expect(page.getByTestId('model-pricing-page')).toBeVisible(); + + await page.getByTestId('model-pricing-add-model').click(); + await expect(page.getByTestId('model-pricing-editor')).toBeVisible(); + + await fillByTestId(page, 'model-pricing-model-name', model); + await fillByTestId(page, 'dimension-key-0', 'duration'); + await fillByTestId(page, 'dimension-source-path-0', 'duration'); + await page.getByTestId('dimension-add').click(); + await fillByTestId(page, 'dimension-key-1', 'quality'); + await fillByTestId(page, 'dimension-source-path-1', 'quality'); + + await page.getByTestId('model-pricing-open-generator').click(); + await expect(page.getByTestId('model-pricing-generator')).toBeVisible(); + await fillByTestId(page, 'generator-base-price', '0.2'); + + const generator = page.getByTestId('model-pricing-generator'); + await generator.locator('input[value="replace_all"]').check({ force: true }); + await fillByTestId(page, 'generator-ratio-value-2', '1'); + await fillByTestId(page, 'generator-ratio-ratio-2', '1.5'); + await expect(page.getByTestId('generator-apply')).toBeEnabled(); + await page.getByTestId('generator-apply').click(); + + const saveResponsePromise = page.waitForResponse( + (response) => + response.request().method() === 'PUT' && + response + .url() + .includes(`/api/option/model_pricing/${encodeURIComponent(model)}`), + ); + await page.getByTestId('model-pricing-save').click(); + const saveResponse = await saveResponsePromise; + expect(saveResponse.ok()).toBeTruthy(); + const saveBody = await saveResponse.json(); + expect(saveBody.success).toBe(true); + + await page.reload(); + await page.getByTestId('model-pricing-tab').click(); + await expect(page.getByTestId('model-pricing-page')).toBeVisible(); + await fillByTestId(page, 'model-pricing-search', model); + await expect(page.getByTestId('model-pricing-table')).toContainText(model); + + await page.getByTestId(`model-pricing-edit-${model}`).click(); + await expect(page.getByTestId('model-pricing-editor')).toBeVisible(); + + const data = await fetchModelPricing(request, model); + expect(data.success).toBe(true); + expect(data.data.schema_version).toBe(1); + expect(data.data.scope).toBe('model'); + expect(data.data.dimensions.map((dimension) => dimension.key)).toEqual([ + 'duration', + 'quality', + ]); + expect(data.data.table.length).toBeGreaterThan(0); + }); +});