This commit is contained in:
jiaojm
2025-11-13 21:13:14 +08:00
repo.diff.parent b3fe3a78e0
repo.diff.commit 109fdd8136
repo.diff.stats_desc%!(EXTRA int=6, int=190, int=99)

repo.diff.view_file

@@ -41,11 +41,12 @@ const config: PlaywrightTestConfig = {
snapshotDir: 'playwright-report/test-snapshots/',
};
if (BASE_URL.includes('http://devstar:3000')) {
console.log(' 已启用安装');
config.globalSetup = require.resolve('./global-setup.ts');
} else {
console.log(` 已跳过 安装`);
}
const skipInstall = env.E2E_SKIP_INSTALL === 'true';
if (skipInstall) {
console.log(`已跳过 global-setup.`);
} else {
console.log(`[Playwright Config] 探测结果: E2E_SKIP_INSTALL is not 'true'. 已启用 global-setup.`);
config.globalSetup = require.resolve('./global-setup.ts');
}
export default config satisfies PlaywrightTestConfig;

repo.diff.view_file

@@ -26,9 +26,12 @@ services:
context: .
# 等待 devstar 的 "healthcheck" 通过后,才启动
environment:
# 将 DevStar 的 URL 传给 Playwright
- DEVSTAR_URL=${DEVSTAR_URL:-http://devstar:3000}
- DOCKER_HOST_GATEWAY=${DOCKER_HOST_GATEWAY}
- DEVSTAR_URL=${DEVSTAR_URL}
- E2E_SKIP_INSTALL=${E2E_SKIP_INSTALL}
- E2E_USERNAME=${E2E_USERNAME}
- E2E_PASSWORD=${E2E_PASSWORD}
- E2E_ADMIN_ID=${E2E_ADMIN_ID}
- E2E_MODE=${E2E_MODE}
volumes:
# 也挂载 Docker Socket
- /var/run/docker.sock:/var/run/docker.sock

repo.diff.view_file

@@ -1,59 +1,70 @@
import { chromium, type FullConfig } from '@playwright/test';async function globalSetup(config: FullConfig) {
const { baseURL } = config.projects[0].use;
import { chromium, type FullConfig } from '@playwright/test';
import { env } from 'node:process';
import { URL } from 'url';
async function globalSetup(config: FullConfig) {
const mode = env.E2E_MODE;
const {baseURL} = config.projects[0].use;
const isInstalledMode=env.E2E_SKIP_INSTALL;
const DEFAULT_E2E_USER = 'testuser';
const DEFAULT_E2E_PASS = '12345678';
if (!baseURL) {
throw new Error('baseURL is not defined in playwright.config.ts');
throw new Error('[GlobalSetup] 致命错误: baseURL 或 storageState 未定义!');
}
const hostGateway = process.env.DOCKER_HOST_GATEWAY || '172.17.0.1';
const giteaBaseURL = `http://${hostGateway}:80`;
const serverDomain = hostGateway;
const browser = await chromium.launch();
const page = await browser.newPage();
try {
await page.goto(baseURL!, { timeout: 15000 });
console.log('[GlobalSetup] 检测到安装界面!正在开始自动化安装...');
await page.getByRole('textbox', { name: 'Server Domain *' }).click();
await page.getByRole('textbox', { name: 'Server Domain *' }).fill(hostGateway);
await page.getByRole('textbox', { name: 'Gitea Base URL *' }).click();
await page.getByRole('textbox', { name: 'Gitea Base URL *' }).fill(giteaBaseURL);
await page.getByText('Server and Third-Party Service Settings').click();
await page.getByRole('checkbox', { name: 'Enable user sign-in via Wechat QR Code.' }).uncheck();
await page.getByRole('checkbox', { name: 'Require a CAPTCHA for user' }).uncheck();
await page.getByText('Administrator Account Settings').click();
await page.getByRole('textbox', { name: 'Administrator Username' }).click();
await page.getByRole('textbox', { name: 'Administrator Username' }).fill('testuser');
await page.getByRole('textbox', { name: 'Email Address' }).click();
await page.getByRole('textbox', { name: 'Email Address' }).fill('ilovcatlyn750314@gmail.com');
await page.getByRole('textbox', { name: 'Password', exact: true }).click();
await page.getByRole('textbox', { name: 'Password', exact: true }).fill('12345678');
await page.getByRole('textbox', { name: 'Confirm Password' }).click();
await page.getByRole('textbox', { name: 'Confirm Password' }).fill('12345678');
await page.getByRole('button', { name: 'Install Gitea'}).click(); // 'Install' (英文) 或 '安装' (中文) 都能匹配
// 4. 等待安装完成
await page.waitForTimeout(240*1000);
console.log('[GlobalSetup] 安装成功!');
} catch (error) {
// 5. 截图并报错
console.error('[GlobalSetup] 自动化安装失败!');
await page.screenshot({ path: 'playwright-report/global-setup-failure.png' });
console.error('[GlobalSetup] 失败截图已保存到: ./reports/global-setup-failure.png');
throw new Error(`[GlobalSetup] 自动化安装失败。查看截图。 \n原始错误: ${error}`);
if (mode === 'url') {
try {
const url1=env.DEVSTAR_URL;
const url2 = `http://${url1}`;
await page.goto(url2, { timeout: 15000 });
console.log('[GlobalSetup] 检测到安装界面!正在开始自动化安装...');
// (你提供的所有 "testuser" 安装步骤...)
await page.getByText('Server and Third-Party Service Settings').click();
await page.getByRole('checkbox', { name: 'Enable user sign-in via Wechat QR Code.' }).uncheck();
await page.getByRole('checkbox', { name: 'Require a CAPTCHA for user' }).uncheck();
await page.getByText('Administrator Account Settings').click();
await page.getByRole('textbox', { name: 'Administrator Username' }).fill('testuser');
await page.getByRole('textbox', { name: 'Email Address' }).fill('ilovcatlyn750314@gmail.com');
await page.getByRole('textbox', { name: 'Password', exact: true }).fill('12345678');
await page.getByRole('textbox', { name: 'Confirm Password' }).fill('12345678');
await page.getByRole('button', { name: 'Install Gitea'}).click();
await page.waitForTimeout(90000);
} catch (error) {
console.error('[GlobalSetup] "URL 模式" 登录失败:', error);
await page.screenshot({ path: 'playwright-report/global-setup-login-failure.png' });
throw error;
}
} else if (mode === 'compose') {
console.log('[GlobalSetup] 模式: 构建(Compose). 正在执行构建模式的安装脚本...');
try {
const hostGateway = env.DOCKER_HOST_GATEWAY || '172.17.0.1';
const giteaBaseURL = `http://${hostGateway}:80`;
const serverDomain = hostGateway;
const browser = await chromium.launch();
await page.goto(baseURL, { timeout: 15000 });
console.log('[GlobalSetup] 检测到安装界面!正在开始自动化安装...');
await page.getByRole('textbox', { name: 'Server Domain *' }).fill(serverDomain);
await page.getByRole('textbox', { name: 'Gitea Base URL *' }).fill(giteaBaseURL);
// (你提供的所有 "testuser" 安装步骤...)
await page.getByText('Server and Third-Party Service Settings').click();
await page.getByRole('checkbox', { name: 'Enable user sign-in via Wechat QR Code.' }).uncheck();
await page.getByRole('checkbox', { name: 'Require a CAPTCHA for user' }).uncheck();
await page.getByText('Administrator Account Settings').click();
await page.getByRole('textbox', { name: 'Administrator Username' }).fill('testuser');
await page.getByRole('textbox', { name: 'Email Address' }).fill('ilovcatlyn750314@gmail.com');
await page.getByRole('textbox', { name: 'Password', exact: true }).fill('12345678');
await page.getByRole('textbox', { name: 'Confirm Password' }).fill('12345678');
await page.getByRole('button', { name: 'Install Gitea'}).click();
await page.waitForTimeout(90000);
} catch (error) {
console.error('[GlobalSetup] "构建模式" 安装失败:', error);
await page.screenshot({ path: 'playwright-report/global-setup-failure.png' });
throw error;
}
} else {
throw new Error(`[GlobalSetup] 未知的 E2E_MODE: "${mode}"`);
}
await browser.close();
}export default globalSetup;
}
export default globalSetup;

repo.diff.view_file

@@ -29,36 +29,52 @@ sudo chmod 666 /var/run/docker.sock
echo "权限设置完成。"
echo ""
echo "===== [3/5] 构建/拉取依赖镜像... ====="
#现在make devstar 处理镜像的构建
docker pull mengning997/webterminal:latest
docker tag mengning997/webterminal:latest devstar.cn/devstar/webterminal:latest
if [ -z "$TARGET_URL" ]; then
echo "===== 现在检测Docker宿主机网关====="
DOCKER_HOST_GATEWAY="172.17.0.1"
if [ "$OS_TYPE" = "Darwin" ]; then
echo "===== [3/5] 构建模式: 构建/拉取依赖镜像... ====="
#现在make devstar 处理镜像的构建
docker pull mengning997/webterminal:latest
docker tag mengning997/webterminal:latest devstar.cn/devstar/webterminal:latest
echo "===== 现在检测Docker宿主机网关====="
DOCKER_HOST_GATEWAY="172.17.0.1"
if [ "$OS_TYPE" = "Darwin" ]; then
DOCKER_HOST_GATEWAY="host.docker.internal"
elif [ "$OS_TYPE" = "Linux" ]; then
elif [ "$OS_TYPE" = "Linux" ]; then
LINUX_IP=$(ip addr show docker0 | grep -Po 'inet \K[\d\.]+')
if [ -n "$LINUX_IP" ]; then
DOCKER_HOST_GATEWAY=$LINUX_IP
else
if [ -n "$LINUX_IP" ]; then
DOCKER_HOST_GATEWAY=$LINUX_IP
else
echo "警告: 无法动态检测 'docker0' IP, 回退到 172.17.0.1"
fi
fi
export DOCKER_HOST_GATEWAY
else
echo "===== [3/5] URL 模式: 跳过依赖镜像和网关检测。 ====="
fi
echo "==> 宿主机网关被设置为: $DOCKER_HOST_GATEWAY"
export DOCKER_HOST_GATEWAY
echo "===== [4/5] 启动并运行测试... ====="
# 检查从 Makefile 传来的 TARGET_URL 变量是否为空
if [ -n "$TARGET_URL" ]; then
# --- [A] URL 模式 ---
echo "URL模式"
# 1. 将目标 URL 导出为 DEVSTAR_URL, 供 docker-compose.yml 读取
echo " 模式: [URL模式]. 目标: $TARGET_URL"
export DEVSTAR_URL=$TARGET_URL
# 2. [关键] 只启动 test-runner 服务
# 我们不需要 devstar, 也不需要 --abort-on-container-exit
export E2E_MODE="url"
echo " 正在检查安装状态..."
PATH_TO_CHECK="/user/login"
EXPECTED_CODE_IF_INSTALLED="200"
PROBE_URL="${TARGET_URL}${PATH_TO_CHECK}"
HTTP_CODE=$(curl -L -s -o /dev/null -w "%{http_code}" "$PROBE_URL")
if [ "$HTTP_CODE" -eq "$EXPECTED_CODE_IF_INSTALLED" ]; then
echo " 探测结果: 目标已安装 (在 ${PROBE_URL} 收到 HTTP ${HTTP_CODE})."
export E2E_SKIP_INSTALL="true"
export E2E_USERNAME
export E2E_PASSWORD
export E2E_ADMIN_ID
else
echo "没有安装,下面执行安装脚本! "
export E2E_SKIP_INSTALL="false"
fi
command docker compose \
-f tests/e2e/docker-compose.test.yml \
up \
@@ -71,6 +87,8 @@ else
# 1. 导出内部 URL (这会成为 compose 里的默认值)
export DEVSTAR_URL="http://devstar:3000"
export E2E_MODE="compose"
export E2E_SKIP_INSTALL="false"
echo "即将执行: docker compose -f tests/e2e/docker-compose.test.yml up --build --wait --exit-code-from test-runner"
# 2. [关键] 运行你原来的命令, 启动所有服务
command docker compose \
@@ -81,7 +99,6 @@ else
--abort-on-container-exit \
--exit-code-from test-runner
fi
# 捕获 test-runner 的退出码
EXIT_CODE=$?
echo ""

repo.diff.view_file

@@ -1,18 +1,28 @@
import { test, expect } from '@playwright/test';
import { link } from 'node:fs';
import { env } from 'node:process';
import { Login } from './utils.e2e.test';
const GITEA_URL= `http://devstar:3000`;
const TEST_USER= `testuser`;
const TEST_PASS= `12345678`;
const TEST_ADMIN_USER_ID=`1`;
const repoName = `e2e-devcontainer-test`;
const isAlreadyInstalled = env.E2E_SKIP_INSTALL === 'true';
const url1=env.DEVSTAR_URL;
const url2 = `http://${url1}`;
const GITEA_URL = (env.E2E_MODE === 'url') ? url2 : 'http://devstar:3000';
const TEST_USER = isAlreadyInstalled ? env.E2E_USERNAME : 'testuser';
const TEST_PASS = isAlreadyInstalled ? env.E2E_PASSWORD : '12345678';
const TEST_ADMIN_USER_ID=isAlreadyInstalled?env.E2E_ADMIN_ID:'1';
const repoName='e2e-test-repo';
test.describe('devcontainer 相关测试',()=>{
test.beforeEach(async ({ page }) => {
await Login(page);
});
test('DevContainer 功能和配置', async ({ page,context }) => {
console.log("正在登陆");
await page.goto(GITEA_URL + '/user/login');
await page.fill('#user_name',TEST_USER);
await page.fill('#password', TEST_PASS);
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL(GITEA_URL + '/');
//console.log("正在登陆");
//await page.goto(GITEA_URL + '/user/login');
//await page.fill('#user_name',TEST_USER);
//await page.fill('#password', TEST_PASS);
//await page.getByRole('button', { name: 'Sign In' }).click();
//await expect(page).toHaveURL(GITEA_URL + '/');
console.log(`正在创建新仓库: ${repoName}`);
await page.goto(GITEA_URL + '/repo/create');
@@ -137,13 +147,13 @@ test('DevContainer 功能和配置', async ({ page,context }) => {
//console.log("test3检查通过");
});
test('权限修改相关', async ({ page }) => {
console.log("正在登陆");
await page.goto(GITEA_URL + '/user/login');
await page.fill('#user_name', TEST_USER);
await page.fill('#password', TEST_PASS);
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL(GITEA_URL + '/');
console.log("登录成功! ");
//console.log("正在登陆");
//await page.goto(GITEA_URL + '/user/login');
//await page.fill('#user_name', TEST_USER);
//await page.fill('#password', TEST_PASS);
//await page.getByRole('button', { name: 'Sign In' }).click();
//await expect(page).toHaveURL(GITEA_URL + '/');
//console.log("登录成功! ");
console.log('权限配置');
await page.goto(GITEA_URL+ '/-/admin/users/' + TEST_ADMIN_USER_ID + '/edit');
@@ -175,5 +185,6 @@ test('权限修改相关', async ({ page }) => {
});
})

repo.diff.view_file

@@ -0,0 +1,48 @@
import { type Page, expect } from '@playwright/test';
import { env } from 'node:process';
const DEFAULT_E2E_USER = 'testuser';
const DEFAULT_E2E_PASS = '12345678';
const mode=env.E2E_MODE;
export async function Login(page: Page) {
const isInstalled = env.E2E_SKIP_INSTALL === 'true';
let username: string | undefined;
let password: string | undefined;
if(mode === 'url'){
const url1=env.DEVSTAR_URL;
const url2 = `http://${url1}`;
if (isInstalled) {
// 【"已安装" 模式
username = env.E2E_USERNAME || DEFAULT_E2E_USER;
password = env.E2E_PASSWORD || DEFAULT_E2E_PASS;
console.log(`"已安装"模式, 尝试用 ${username} 登录...`);
} else {
// 【"未安装" 模式】
username = DEFAULT_E2E_USER;
password = DEFAULT_E2E_PASS;
console.log(`[LoginHelper] "刚安装"模式, 尝试用 ${username} 登录...`);
}
// 4. (检查验证码和执行登录的逻辑)
await page.goto(url2 + '/user/login');
const captchaInput = page.locator('input[name="captcha"]');
if (await captchaInput.isVisible()) {
throw new Error('登录失败: 检测到验证码! 请禁用它。');
}
await page.fill('#user_name',username);
await page.fill('#password', password);
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL(url2 + '/');
console.log(`[LoginHelper] 登录成功!`);
}
else if(mode=='compose'){
await page.fill('#user_name',username);
await page.fill('#password', password);
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL(env.DEVSTAR_URL+ '/');
console.log(`[LoginHelper] 登录成功!`);
}
}