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.
135 lines
8.2 KiB
135 lines
8.2 KiB
const assert = require('node:assert/strict');
|
|
const { readFileSync } = require('node:fs');
|
|
const { join } = require('node:path');
|
|
const { chromium, request } = require('playwright');
|
|
|
|
if (process.env.YOUTHOME_LIVE_BACKEND !== '1') {
|
|
console.error('Set YOUTHOME_LIVE_BACKEND=1 to run the write-and-cleanup test.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const webBase = process.env.YOUTHOME_WEB_URL || 'http://127.0.0.1:4173/pages';
|
|
const apiBase = process.env.YOUTHOME_BACKEND_URL || 'http://127.0.0.1:8009';
|
|
const png = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg==', 'base64');
|
|
const video = readFileSync(join(__dirname, 'fixtures', 'demo-video.mp4'));
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({ channel: 'msedge', headless: true });
|
|
const api = await request.newContext({ baseURL: apiBase, timeout: 120000 });
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
const phone = `199${String(Date.now()).slice(-8)}`;
|
|
const password = `Webtest${String(Date.now()).slice(-6)}a`;
|
|
const uploads = new Set();
|
|
const pageErrors = [];
|
|
let token;
|
|
let videoId;
|
|
let activityId;
|
|
let commentId;
|
|
page.on('pageerror', (error) => pageErrors.push(error.message));
|
|
page.on('response', async (response) => {
|
|
if (!response.url().includes('/api/file/upload/') || !response.ok()) return;
|
|
try {
|
|
const body = await response.json();
|
|
const url = body.data?.url || body.url;
|
|
if (url) uploads.add(url);
|
|
} catch (error) { /* a failed upload is asserted by the publishing flow */ }
|
|
});
|
|
const cleanup = async (label, path) => {
|
|
try {
|
|
const response = await api.delete(path, { headers: { access_token: token } });
|
|
console.log(`${label} cleanup: HTTP ${response.status()}`);
|
|
if (!response.ok()) process.exitCode = 1;
|
|
} catch (error) {
|
|
console.error(`${label} cleanup failed: ${error.message}`);
|
|
process.exitCode = 1;
|
|
}
|
|
};
|
|
try {
|
|
const registration = await api.post('/api/auth/register', {
|
|
data: { phone, password, captcha: '111111', universityName: '复旦大学', nickname: '网页发布回归', platform: 'WEB' }
|
|
});
|
|
const registered = await registration.json();
|
|
assert.equal(registered.code, 200, JSON.stringify(registered));
|
|
token = registered.data.token;
|
|
|
|
await page.goto(`${webBase}/login.html?next=publish-video.html&demo=0`);
|
|
await page.locator('#panel-password input[type="tel"]').fill(phone);
|
|
await page.locator('#panel-password input[type="password"]').fill(password);
|
|
await page.locator('#panel-password .btn-primary').click();
|
|
await page.waitForURL('**/publish-video.html');
|
|
await page.locator('.web-video-input').setInputFiles({ name: 'demo-video.mp4', mimeType: 'video/mp4', buffer: video });
|
|
await page.locator('.web-video-preview').waitFor({ state: 'visible' });
|
|
await page.locator('input[placeholder*="视频标题"]').fill('真实接口视频回归');
|
|
await page.locator('textarea[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-primary').click();
|
|
await page.waitForURL('**/video-detail.html?id=*', { timeout: 120000 });
|
|
videoId = new URL(page.url()).searchParams.get('id');
|
|
await page.waitForFunction(() => document.querySelector('.video-title')?.textContent === '真实接口视频回归');
|
|
assert.ok((await page.locator('.video-desc').innerText()).includes('真实接口视频简介回归'));
|
|
await page.locator('.video-player-wrap video').waitFor();
|
|
await page.waitForFunction(() => document.querySelector('.video-player-wrap video')?.videoWidth > 0, null, { timeout: 30000 });
|
|
await page.locator('.comment-textarea').fill('真实接口视频评论');
|
|
await page.locator('.comment-submit-btn').click();
|
|
await page.locator('.comment-list .comment-item').filter({ hasText: '真实接口视频评论' }).waitFor();
|
|
const commentsResponse = await api.get(`/api/campus/moments/comments?momentId=${encodeURIComponent(videoId)}&page=1&size=50`, { headers: { access_token: token } });
|
|
const commentsResult = await commentsResponse.json();
|
|
commentId = commentsResult.data?.items?.find((item) => item.content === '真实接口视频评论')?.id;
|
|
assert.ok(commentId, JSON.stringify(commentsResult));
|
|
console.log('Real video publish, description and comment passed');
|
|
|
|
await page.goto(`${webBase}/activity-create.html`);
|
|
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: 'activity-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} 18:00`;
|
|
document.getElementById('end-time').value = `${day} 19:00`;
|
|
document.getElementById('registration-deadline').value = `${day} 17:00`;
|
|
});
|
|
await page.locator('#location-name').fill('复旦大学体育馆');
|
|
await page.locator('#location-address').fill('上海市杨浦区邯郸路220号');
|
|
await page.locator('.btn-next').click();
|
|
await page.waitForURL('**/activity-detail.html?id=*', { timeout: 120000 });
|
|
activityId = new URL(page.url()).searchParams.get('id');
|
|
await page.waitForFunction(() => document.querySelector('.cover-title')?.textContent.trim() === '真实接口活动回归');
|
|
assert.ok((await page.locator('.side-info-item').filter({ hasText: '报名截止' }).innerText()).includes('17:00'));
|
|
const createdResponse = await api.get('/api/campus/activities/created?page=1&size=100', { headers: { access_token: token } });
|
|
const createdData = await createdResponse.json();
|
|
const createdActivity = createdData.data?.items?.find((item) => item.id === activityId);
|
|
console.log('Created activity media fields:', JSON.stringify(Object.fromEntries(Object.entries(createdActivity || {}).filter(([key]) => /cover|image|media|banner/i.test(key)))));
|
|
assert.ok(createdActivity?.coverImage);
|
|
const coverState = await page.locator('.activity-detail-cover').evaluate(async (element) => {
|
|
const background = getComputedStyle(element).backgroundImage;
|
|
const url = background.match(/^url\(["']?(.+?)["']?\)$/)?.[1];
|
|
if (!url) return { background, loaded: false };
|
|
const image = new Image(); image.src = url;
|
|
try { await image.decode(); return { background, loaded: image.naturalWidth > 0 }; }
|
|
catch (error) { return { background, loaded: false }; }
|
|
});
|
|
console.log('Activity cover state:', JSON.stringify(coverState));
|
|
assert.ok(coverState.loaded);
|
|
assert.deepEqual(pageErrors, []);
|
|
console.log('Real activity publish, deadline and detail passed');
|
|
} finally {
|
|
if (token && commentId && videoId) await cleanup('Comment', `/api/campus/moments/comments?momentId=${encodeURIComponent(videoId)}&commentId=${encodeURIComponent(commentId)}`);
|
|
if (token && videoId) await cleanup('Video', `/api/campus/moments?id=${encodeURIComponent(videoId)}`);
|
|
if (token && activityId) await cleanup('Activity', `/api/campus/activities?id=${encodeURIComponent(activityId)}`);
|
|
if (token) for (const url of uploads) await cleanup('File', `/api/file/delete?url=${encodeURIComponent(url)}`);
|
|
if (token) {
|
|
try {
|
|
const response = await api.post('/api/users/deactivate?confirm=true', { headers: { access_token: token } });
|
|
console.log(`Temporary account cleanup: HTTP ${response.status()}`);
|
|
if (!response.ok()) process.exitCode = 1;
|
|
} catch (error) { console.error(`Temporary account cleanup failed: ${error.message}`); process.exitCode = 1; }
|
|
}
|
|
await api.dispose();
|
|
await browser.close();
|
|
}
|
|
})().catch((error) => { console.error(error); process.exitCode = 1; });
|
|
|