Files
LinkPort/web/tests/workspace-features.spec.ts

117 lines
8.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test';
test('未应用 URL 自动参与测试和保存,关闭结果清理状态且参数可回读', async ({page}) => {
await page.goto('/');
const session = await (await page.request.get('/api/v1/session')).json();
const headers = {'X-CSRF-Token':session.csrfToken};
let id = '';
try {
await page.getByRole('navigation',{name:'工作空间菜单'}).getByRole('button',{name:'目标服务',exact:true}).click();
await page.getByRole('button',{name:'+ 新建目标',exact:true}).click();
const editor = page.getByRole('dialog',{name:'新建目标服务',exact:true});
await editor.locator('[data-field="name"] input').fill('参数与标识回归');
await editor.getByLabel('连接 URL',{exact:true}).fill('mysql://reader:isolated-secret@fixture.invalid/app?charset=latin1&tls=false');
await page.route('**/api/v1/targets/test',async route => {
const body = route.request().postDataJSON();
expect(body.config).toMatchObject({targetHost:'fixture.invalid',database:'app',connectionOptions:{charset:'latin1'}});
expect(body.config.accounts[0].password).toBe('isolated-secret');
await route.fulfill({json:{version:'fixture',tls:false}});
});
await editor.getByRole('button',{name:'测试连接',exact:true}).click();
const details = page.getByRole('dialog',{name:'连接测试',exact:true});
await expect(details).toContainText('fixture');
await details.locator('.workspace-page__footer').getByRole('button',{name:'关闭',exact:true}).click();
await expect(details).not.toBeVisible();
await expect(editor.locator('.test-summary')).toHaveCount(0);
await expect(editor.getByTestId('connection-stages')).toHaveCount(0);
await editor.getByLabel('URL 参数',{exact:true}).fill('charset=utf8mb4&tls=false&database=next');
await expect(editor.locator('.test-summary')).toHaveCount(0);
await editor.getByLabel('连接备注',{exact:true}).fill('回归测试用途');
await editor.locator('label.el-checkbox').filter({hasText:'在资源列表和侧栏显示备注'}).click();
await expect(editor.getByRole('checkbox',{name:'在资源列表和侧栏显示备注'})).toBeChecked();
await editor.getByRole('tab',{name:'高级',exact:true}).click();
await editor.getByLabel('网络连接超时',{exact:true}).fill('8');
await expect(editor.getByLabel('查询超时',{exact:true})).toBeDisabled();
const saving = page.waitForResponse(response => response.url().endsWith('/api/v1/targets') && response.request().method() === 'POST');
await editor.getByRole('button',{name:'保存配置',exact:true}).click();
const response = await saving;
expect(response.ok()).toBeTruthy(); id = (await response.json()).config.id;
const row = page.locator('.target-card').filter({hasText:'参数与标识回归'});
await expect(row).toContainText('回归测试用途');
await row.getByRole('button',{name:'配置目标',exact:true}).click();
const edit = page.getByRole('dialog',{name:'编辑目标服务',exact:true});
await expect(edit.getByLabel('URL 参数',{exact:true})).toHaveValue(/charset=utf8mb4/);
const saved = await (await page.request.get(`/api/v1/targets/${id}`)).json();
expect(saved.config).toMatchObject({database:'next',remark:'回归测试用途',showRemark:true,connectionOptions:{charset:'utf8mb4',connectTimeoutSeconds:8}});
expect(JSON.stringify(await page.evaluate(() => ({...localStorage})))).not.toContain('isolated-secret');
} finally { await page.unrouteAll({behavior:'ignoreErrors'}).catch(()=>{}); if (id) await page.request.delete(`/api/v1/targets/${id}`,{headers}).catch(()=>{}); }
});
test('搜索和树导航复用资源标签,切换保护草稿且最近记录仅存标识', async ({page}) => {
await page.goto('/');
const session = await (await page.request.get('/api/v1/session')).json();
const headers = {'X-CSRF-Token':session.csrfToken};
const response = await page.request.post('/api/v1/targets',{headers,data:{name:'工作区导航回归',project:'导航回归项目',environment:'test',type:'mysql',targetHost:'fixture.invalid',targetPort:3306,color:'#22c55e',accounts:[{id:'account',remark:'account',username:'reader',password:'navigation-fixture-secret'}]}});
expect(response.ok()).toBeTruthy(); const id = (await response.json()).config.id;
try {
await page.reload();
await expect(page.getByRole('button',{name:'搜索全部资源',exact:true})).toBeVisible();
await page.keyboard.press('Control+k');
const search = page.getByRole('dialog',{name:'搜索资源 · 当前项目与环境'});
await search.getByRole('combobox',{name:'搜索资源关键词'}).fill('工作区导航回归');
await search.getByRole('combobox',{name:'搜索资源关键词'}).press('Enter');
const editor = page.getByRole('dialog',{name:'编辑目标服务',exact:true});
await expect(editor).toBeVisible();
await editor.getByLabel('连接备注',{exact:true}).fill('unsaved');
await editor.getByRole('button',{name:'关闭当前标签',exact:true}).click();
await expect(page.getByRole('dialog',{name:'未保存的更改',exact:true})).toBeVisible();
await page.getByRole('button',{name:'继续编辑',exact:true}).click();
await expect(editor.getByLabel('连接备注',{exact:true})).toHaveValue('unsaved');
await editor.getByRole('button',{name:'关闭当前标签',exact:true}).click();
await page.getByRole('button',{name:'放弃更改',exact:true}).click();
await expect(editor).not.toBeVisible();
await page.getByRole('navigation',{name:'工作空间菜单'}).getByRole('button',{name:'运行概览',exact:true}).click();
await expect(page.locator('.workspace-home')).toContainText('工作区导航回归');
const history = JSON.parse(await page.evaluate(() => localStorage.getItem('linkport.recent.v1')) || '[]');
expect(history.find((item:any) => item.id === id)).toMatchObject({kind:'targets',id});
expect(Object.keys(history.find((item:any) => item.id === id)).sort()).toEqual(['at','id','kind']);
await page.setViewportSize({width:390,height:844});
await page.getByRole('tab', {name:'资源',exact:true}).click();
await page.getByRole('button',{name:'展开资源树',exact:true}).click();
const tree = page.getByRole('dialog',{name:'资源树',exact:true});
await expect(tree).toContainText('导航回归项目');
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth-innerWidth)).toBeLessThanOrEqual(1);
} finally { await page.request.delete(`/api/v1/targets/${id}`,{headers}).catch(()=>{}); }
});
test('表格列显隐、排序和宽度偏好可恢复,右键菜单有键盘入口', async ({page}) => {
await page.goto('/');
await page.getByRole('button',{name:'表格列设置',exact:true}).click();
const picker = page.getByLabel('表格列设置面板',{exact:true});
await picker.locator('label.el-checkbox').filter({has:page.getByRole('checkbox',{name:'目标服务',exact:true})}).click();
await expect(picker.getByRole('checkbox',{name:'目标服务',exact:true})).not.toBeChecked();
await picker.getByRole('button',{name:'上移状态列',exact:true}).click();
await picker.getByRole('spinbutton',{name:'状态列宽',exact:true}).fill('160');
await page.getByRole('heading',{name:'网关服务',exact:true}).click();
await page.reload();
await expect(page.locator('.proxy-table .el-table__header th').filter({hasText:'目标服务'})).toHaveCount(0);
await page.getByRole('button',{name:'表格列设置',exact:true}).click();
await picker.getByRole('button',{name:'恢复默认列',exact:true}).click();
await page.getByRole('heading',{name:'网关服务',exact:true}).click();
await expect(page.locator('.proxy-table .el-table__header th').filter({hasText:'目标服务'})).toBeVisible();
const session = await (await page.request.get('/api/v1/session')).json();
const headers = {'X-CSRF-Token':session.csrfToken};
const response = await page.request.post('/api/v1/targets',{headers,data:{name:'菜单回归目标',type:'mysql',targetHost:'fixture.invalid',targetPort:3306,accounts:[{id:'account',remark:'account',username:'reader',password:'fixture-secret'}]}});
expect(response.ok()).toBeTruthy(); const id = (await response.json()).config.id;
try {
await page.reload();
await page.getByRole('navigation',{name:'工作空间菜单'}).getByRole('button',{name:'目标服务',exact:true}).click();
await page.locator('.target-card').filter({hasText:'菜单回归目标'}).click({button:'right'});
const menu = page.getByRole('menu',{name:'资源操作',exact:true});
await expect(menu.getByRole('menuitem',{name:'编辑配置',exact:true})).toBeFocused();
await page.keyboard.press('End');
await expect(menu.getByRole('menuitem',{name:'删除目标',exact:true})).toBeFocused();
await page.keyboard.press('Escape'); await expect(menu).not.toBeVisible();
} finally { await page.request.delete(`/api/v1/targets/${id}`,{headers}); }
});