lowcode-test/tests/flow.spec.ts

232 lines
11 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.beforeEach(async ({ request }) => {
await expect
.poll(
async () => {
try {
return (await request.get('http://localhost:5174')).status();
} catch {
return 0;
}
},
{ timeout: 20_000 },
)
.toBe(200);
});
test('draft, immutable publish and mobile update', async ({ page, context, request }) => {
const api = 'http://localhost:3000/api/pages/demo-store';
const draft = await request.get(api + '/draft').then((r) => r.json());
await request.put(api + '/draft', { data: { ...draft, blocks: [] } });
await page.goto('/');
await page.getByText('标题', { exact: true }).first().dragTo(page.locator('.phone'));
await expect(page.locator('.materials li')).toHaveCount(1);
await page.getByRole('button', { name: '发布' }).click();
await expect(page.locator('.saved')).toContainText('已发布版本');
await expect(page.getByRole('status')).toContainText(/发布成功 · 当前版本 v\d+/);
const first = await request.get(api + '/published').then((r) => r.json());
const mobile = await context.newPage();
await mobile.goto('http://localhost:5174/?id=demo-store');
await expect(mobile.locator('h2')).toHaveCount(1);
await page.locator('.materials li').first().click();
await page.getByRole('button', { name: '复制' }).click();
await page.getByRole('button', { name: '保存草稿' }).click();
await mobile.reload();
await expect(mobile.locator('h2')).toHaveCount(1);
expect((await request.get(api + '/published').then((r) => r.json())).version).toBe(first.version);
await page.getByRole('button', { name: '发布' }).click();
await expect
.poll(async () => (await request.get(api + '/published').then((r) => r.json())).version)
.toBe(first.version + 1);
await mobile.reload();
await expect(mobile.locator('h2')).toHaveCount(2);
expect((await request.get(api + '/published').then((r) => r.json())).version).toBe(
first.version + 1,
);
});
test('two-column regions can be edited independently', async ({ page, request }) => {
const api = 'http://localhost:3000/api/pages/demo-store';
const draft = await request.get(api + '/draft').then((r) => r.json());
await request.put(api + '/draft', { data: { ...draft, blocks: [] } });
await page.goto('/');
await page.getByText('双列容器', { exact: true }).first().click();
await page.getByLabel('左栏内容').fill('左侧活动专区');
await page.getByLabel('右栏内容').fill('右侧新品推荐');
const preview = page.frameLocator('iframe[title="H5 实时预览"]');
await expect(preview.locator('.cols div').first()).toHaveText('左侧活动专区');
await expect(preview.locator('.cols div').nth(1)).toHaveText('右侧新品推荐');
});
test('all material-specific editors update the shared preview', async ({ page, request }) => {
const api = 'http://localhost:3000/api/pages/demo-store';
const draft = await request.get(api + '/draft').then((r) => r.json());
await request.put(api + '/draft', { data: { ...draft, blocks: [] } });
await page.goto('/');
const material = (name: string) =>
page.locator('.material-grid').getByRole('button', { name, exact: true });
const preview = page.frameLocator('iframe[title="H5 实时预览"]');
await material('横幅 Banner').click();
await page.getByLabel('Banner 主标题').fill('可编辑横幅');
await page.getByLabel('Banner 高度').fill('220');
await expect(preview.locator('.r-banner h1')).toHaveText('可编辑横幅');
await expect(preview.locator('.r-banner')).toHaveCSS('height', '220px');
await material('标题').click();
await page.getByLabel('标题文字').fill('可编辑标题');
await page.getByLabel('标题字号').fill('32');
await expect(preview.locator('h2')).toHaveText('可编辑标题');
await expect(preview.locator('h2')).toHaveCSS('font-size', '32px');
await material('文本').click();
await page.getByLabel('正文内容').fill('正文内容与排版均可调整');
await page.getByLabel('正文行高').fill('2');
await expect(preview.locator('.r-text')).toHaveText('正文内容与排版均可调整');
await expect(preview.locator('.r-text')).toHaveCSS('line-height', '28px');
await material('图片').click();
await page.getByLabel('图片说明').fill('编辑后的图片说明');
await page.getByLabel('图片高度').fill('180');
await page.getByLabel('图片圆角').fill('16');
await expect(preview.locator('.r-image')).toHaveAttribute('alt', '编辑后的图片说明');
await expect(preview.locator('.r-image')).toHaveCSS('height', '180px');
await material('按钮').click();
await page.getByLabel('按钮文字', { exact: true }).fill('查看详情');
await page.getByLabel('按钮背景颜色').fill('#245f57');
await page.getByLabel('点击行为').selectOption('link');
await page.getByLabel('目标链接').fill('/detail');
await expect(preview.locator('.r-button')).toHaveText('查看详情');
await expect(preview.locator('.r-button')).toHaveCSS('background-color', 'rgb(36, 95, 87)');
await material('商品列表').click();
await page.getByLabel('商品 1 名称').fill('手工咖啡杯');
await page.getByRole('button', { name: '添加商品' }).click();
await page.getByLabel('商品 3 名称').fill('夏日随行杯');
await page.getByLabel('商品列数').selectOption('1');
await expect(preview.locator('.products article')).toHaveCount(3);
await expect(preview.locator('.products article').first().locator('b')).toHaveText('手工咖啡杯');
await expect(preview.locator('.products')).toHaveCSS('grid-template-columns', '350px');
await material('分割线').click();
await page.getByLabel('分割线粗细').fill('4');
await page.getByLabel('分割线线型').selectOption('dashed');
await expect(preview.locator('hr')).toHaveCSS('border-top-width', '4px');
await expect(preview.locator('hr')).toHaveCSS('border-top-style', 'dashed');
await material('间距').click();
await page.getByLabel('间距高度').evaluate((el) => {
const input = el as HTMLInputElement;
input.value = '96';
input.dispatchEvent(new Event('input', { bubbles: true }));
});
await expect(preview.locator('.r-spacer')).toHaveCSS('height', '96px');
await material('双列容器').click();
await page.getByLabel('左栏内容').fill('品牌故事');
await page.getByLabel('右栏内容').fill('新品指南');
await page.getByLabel('双列间距').fill('20');
await expect(preview.locator('.cols div').first()).toHaveText('品牌故事');
await expect(preview.locator('.cols')).toHaveCSS('gap', '20px');
});
test('banner supports multiple images, looping and autoplay interval', async ({
page,
request,
}) => {
const api = 'http://localhost:3000/api/pages/demo-store';
const draft = await request.get(api + '/draft').then((r) => r.json());
await request.put(api + '/draft', { data: { ...draft, blocks: [] } });
await page.goto('/');
await page
.locator('.material-grid')
.getByRole('button', { name: '横幅 Banner', exact: true })
.click();
await page.getByRole('button', { name: '添加轮播图片' }).click();
await page
.getByLabel('Banner 图片 2', { exact: true })
.fill('https://example.com/banner-two.webp');
await page.getByLabel('Banner 图片 2 说明').fill('第二张轮播图');
await page.getByLabel('开启轮播').check();
const preview = page.frameLocator('iframe[title="H5 实时预览"]');
const image = preview.locator('.carousel-slide');
await expect(preview.locator('.carousel-dots button')).toHaveCount(2);
await preview.getByRole('button', { name: '下一张' }).click();
await expect(image).toHaveAttribute('src', 'https://example.com/banner-two.webp');
await expect(image).toHaveAttribute('alt', '第二张轮播图');
await preview.getByRole('button', { name: '上一张' }).click();
await page.getByLabel('循环播放').uncheck();
await page.getByLabel('自动轮播').check();
await page.getByLabel('自动轮播时间').fill('1');
await expect
.poll(() => image.getAttribute('src'), { timeout: 2500 })
.toBe('https://example.com/banner-two.webp');
await page.waitForTimeout(1200);
await expect(image).toHaveAttribute('src', 'https://example.com/banner-two.webp');
await page.getByLabel('循环播放').check();
await expect
.poll(() => image.getAttribute('src'), { timeout: 2500 })
.not.toBe('https://example.com/banner-two.webp');
});
test('editor and H5 service URLs follow the current LAN host', async ({ page }) => {
await page.goto('http://127.0.0.1:5173');
await expect(page.locator('iframe[title="H5 实时预览"]')).toHaveAttribute(
'src',
'http://127.0.0.1:5174/?preview=1',
);
await expect(page.getByRole('link', { name: '打开移动端' })).toHaveAttribute(
'href',
'http://127.0.0.1:5174/?id=demo-store',
);
await page.goto('http://127.0.0.1:5174/?id=demo-store');
await expect(page.locator('.page-renderer')).toBeVisible();
});
test('benefit library keeps marketing materials and renders direct recharge components', async ({
page,
request,
}) => {
const api = 'http://localhost:3000/api/pages/demo-store';
const draft = await request.get(api + '/draft').then((r) => r.json());
await request.put(api + '/draft', { data: { ...draft, blocks: [] } });
await page.goto('/');
await expect(page.getByRole('button', { name: '营销组件', exact: true })).toHaveClass(/active/);
await expect(page.getByRole('button', { name: '横幅 Banner', exact: true })).toBeVisible();
await page.getByRole('button', { name: /直充 \/ 卡密/ }).click();
await expect(page.getByRole('button', { name: /直充 \/ 卡密/ })).toHaveClass(/active/);
await expect(page.getByRole('button', { name: '权益商品卡片', exact: true })).toBeVisible();
await page.getByRole('button', { name: '添加业务示例组合' }).click();
const preview = page.frameLocator('iframe[title="H5 实时预览"]');
await expect(page.locator('.materials li')).toHaveCount(4);
await expect(preview.locator('.benefit-product-card')).toBeVisible();
await expect(preview.locator('.benefit-actions')).toBeVisible();
await expect(preview.locator('.benefit-notice')).toBeVisible();
await expect(preview.locator('.benefit-product-list')).toBeVisible();
await page.locator('.materials li').first().click();
await page.getByLabel('权益商品名称').fill('网易云音乐黑胶月卡');
await page.getByLabel('权益交付方式').selectOption('code');
await page.getByLabel('权益业务标签').fill('卡密');
await expect(preview.locator('.benefit-product-card .benefit-name-row b')).toHaveText(
'网易云音乐黑胶月卡',
);
await expect(preview.locator('.benefit-product-card .delivery-badge')).toHaveText('卡密');
await page.getByRole('button', { name: '营销组件', exact: true }).click();
await expect(page.getByRole('button', { name: '横幅 Banner', exact: true })).toBeVisible();
await expect(page.locator('.materials li')).toHaveCount(4);
});