You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

132 lines
8.7 KiB

const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const { join } = require('node:path');
const { chromium } = require('playwright');
const base = process.env.YOUTHOME_WEB_URL || 'http://127.0.0.1:4173/pages';
const png = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg==', 'base64');
const localDate = (offsetMinutes) => {
const date = new Date(Date.now() + offsetMinutes * 60000);
const pad = (value) => String(value).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
};
(async () => {
const browser = await chromium.launch({ channel: 'msedge', headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, locale: 'zh-CN' });
const errors = [];
const apiCalls = [];
page.on('pageerror', (error) => errors.push(error.stack || error.message));
page.on('request', (request) => { if (request.url().includes('/api/')) apiCalls.push(request.url()); });
try {
await page.goto(`${base}/feed.html?demo=1`);
assert.equal(await page.evaluate(() => window.YouthomeApi.source()), 'local');
assert.ok(await page.evaluate(() => window.YouthomeLocal.account()));
await page.goto(`${base}/publish-post.html`);
await page.locator('.post-textarea').fill('离线演示动态:发布后可以查看详情。');
await page.locator('.web-publish-actions .btn-primary').click();
await page.waitForURL('**/post-detail.html?moment=*');
await page.waitForFunction(() => document.querySelector('.post-detail-content')?.textContent.includes('离线演示动态'));
assert.ok((await page.locator('.post-detail-content').innerText()).includes('离线演示动态'));
await page.goto(`${base}/publish-video.html`);
await page.locator('.web-video-input').setInputFiles({ name: 'demo-video.mp4', mimeType: 'video/mp4', buffer: readFileSync(join(__dirname, 'fixtures', 'demo-video.mp4')) });
await page.locator('.web-video-preview').waitFor({ state: 'visible' });
await page.waitForFunction(() => document.querySelector('.web-video-preview')?.videoWidth > 0);
await page.locator('input[placeholder*="视频标题"]').fill('离线演示视频');
await page.locator('textarea[placeholder*="介绍一下"]').fill('可播放的视频内容');
await page.getByText('从视频截取').click();
await page.locator('.cover-thumb.has-cover').waitFor();
await page.locator('.web-publish-actions .btn-primary').click();
await page.waitForURL('**/video-detail.html?id=*');
await page.locator('.video-player-wrap video').waitFor();
await page.waitForFunction(() => document.querySelector('.video-player-wrap video')?.videoWidth > 0);
assert.ok((await page.locator('.video-title').first().innerText()).includes('离线演示视频'));
assert.equal((await page.locator('.video-desc').innerText()).includes('可播放的视频内容'), true);
await page.locator('.comment-textarea').fill('视频下的演示评论');
await page.locator('.comment-submit-btn').click();
await page.locator('.comment-list .comment-item').filter({ hasText: '视频下的演示评论' }).waitFor();
assert.equal(await page.locator('.video-actions .action-btn').nth(1).locator('.count').innerText(), '1');
await page.reload();
await page.locator('.comment-list .comment-item').filter({ hasText: '视频下的演示评论' }).waitFor();
await page.goto(`${base}/video.html`);
assert.ok((await page.locator('.web-live-video').first().innerText()).includes('离线演示视频'));
await page.goto(`${base}/publish-video.html`);
await page.locator('.web-video-input').setInputFiles({ name: 'demo-video.mp4', mimeType: 'video/mp4', buffer: readFileSync(join(__dirname, 'fixtures', 'demo-video.mp4')) });
await page.locator('input[placeholder*="视频标题"]').fill('上传封面演示视频');
await page.locator('.web-cover-input').setInputFiles({ name: 'cover.png', mimeType: 'image/png', buffer: png });
await page.locator('.cover-thumb.has-cover').waitFor();
await page.locator('.web-publish-actions .btn-secondary').click();
await page.locator('.web-toast').getByText('视频草稿已保存').waitFor();
await page.reload();
await page.locator('.web-video-preview').waitFor({ state: 'visible' });
await page.locator('.cover-thumb.has-cover').waitFor();
assert.equal(await page.locator('input[placeholder*="视频标题"]').inputValue(), '上传封面演示视频');
await page.locator('.web-publish-actions .btn-primary').click();
await page.waitForURL('**/video-detail.html?id=*');
await page.locator('.video-player-wrap video').waitFor();
assert.ok((await page.locator('.video-title').first().innerText()).includes('上传封面演示视频'));
await page.goto(`${base}/activity-create.html`);
await page.getByRole('button', { name: '选择活动开始时间' }).click();
await page.locator('.web-date-dialog').waitFor();
await page.getByRole('button', { name: '确定' }).click();
assert.ok(await page.locator('#start-time').inputValue());
await page.locator('#activity-title').fill('离线演示组局活动');
await page.locator('#activity-desc').fill('一起在校园完成一场线下活动。');
await page.locator('#activity-type').selectOption('sports');
await page.locator('.web-activity-cover-input').setInputFiles({ name: 'cover.png', mimeType: 'image/png', buffer: png });
await page.locator('#cover-upload.has-cover').waitFor();
await page.evaluate(() => {
const date = new Date(Date.now() + 3 * 86400000);
const pad = (value) => String(value).padStart(2, '0');
const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
document.getElementById('start-time').value = `${day}T18:00`;
document.getElementById('end-time').value = `${day}T19:30`;
document.getElementById('registration-deadline').value = `${day}T17:00`;
});
await page.locator('#location-name').fill('校园体育馆');
await page.locator('.location-locate-btn').click();
await page.locator('.web-map-dialog .web-map-canvas').waitFor();
assert.equal(await page.locator('.web-map-dialog').evaluate((node) => node.open), true);
await page.locator('.web-map-dialog [aria-label="关闭"]').click();
await page.locator('.btn-draft').click();
await page.locator('.web-toast').getByText('活动草稿已保存').waitFor();
await page.reload();
assert.equal(await page.locator('#activity-title').inputValue(), '离线演示组局活动');
await page.locator('#cover-upload.has-cover').waitFor();
assert.ok(await page.locator('#registration-deadline').inputValue());
await page.locator('.btn-preview').click();
await page.locator('.web-activity-preview').waitFor();
await page.getByText('关闭预览').click();
await page.locator('.btn-next').click();
await page.waitForURL('**/activity-detail.html?id=*');
assert.equal((await page.locator('.cover-title').innerText()).trim(), '离线演示组局活动');
assert.ok((await page.locator('.side-info-item').filter({ hasText: '报名截止' }).innerText()).includes('2026'));
await page.goto(`${base}/activities.html`);
assert.ok((await page.locator('.web-live-activity').first().innerText()).includes('离线演示组局活动'));
await page.setViewportSize({ width: 390, height: 844 });
for (const name of ['publish-post', 'publish-video', 'activity-create']) {
await page.goto(`${base}/${name}.html`);
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - innerWidth);
assert.ok(overflow <= 0, `${name} overflows by ${overflow}px at 390px`);
}
await page.getByRole('button', { name: '选择活动开始时间' }).click();
assert.ok(await page.locator('.web-date-dialog').evaluate((node) => node.getBoundingClientRect().right <= innerWidth));
await page.locator('.web-date-dialog [aria-label="关闭"]').click();
await page.locator('.location-locate-btn').click();
assert.ok(await page.locator('.web-map-dialog').evaluate((node) => node.getBoundingClientRect().right <= innerWidth));
await page.locator('.web-map-dialog [aria-label="关闭"]').click();
assert.deepEqual(apiCalls, []);
assert.deepEqual(errors, []);
await page.goto(`${base}/login.html?demo=0`);
assert.equal(await page.evaluate(() => window.YouthomeApi.source()), 'api');
assert.equal(await page.evaluate(() => window.YouthomeApi.token()), null);
console.log('offline post, video, cover capture, activity publish and detail passed');
} finally {
await browser.close();
}
})().catch((error) => { console.error(error); process.exitCode = 1; });