|
|
|
|
@@ -449,12 +449,11 @@ async function installCodingFirstChatHost(
|
|
|
|
|
...overrides,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const teacherDefinition = {schema_version:1,teacher_id:'coding-teacher',name:'代码老师',description:'',avatar_id:'avatar-01',welcome_message:'一起理解代码',suggested_questions:['解释当前代码'],system_prompt:'教学',skills:[],model:{model_id:'model-a',reasoning_choice:{mode:'default'}},limits:{max_input_tokens:8000,max_output_tokens:1500}};
|
|
|
|
|
const teacherDefinition = {schema_version:1,teacher_id:'coding-teacher',name:'代码智能体',description:'',avatar_id:'avatar-01',welcome_message:'一起理解代码',suggested_questions:['解释当前代码'],system_prompt:'教学',skills:[],model:{model_id:'model-a',reasoning_choice:{mode:'default'}},limits:{max_input_tokens:8000,max_output_tokens:1500}};
|
|
|
|
|
const teachers = [
|
|
|
|
|
{ teacher_id: 'teacher-code', version: 1, is_default: true, definition: { ...teacherDefinition, config_id: 'teacher-code', runtime: 'yuxi', system_prompt: '', yuxi: { agent_slug: 'code', agent_version: 2 } } },
|
|
|
|
|
{ teacher_id: 'teacher-algorithm', version: 9, is_default: false, definition: { ...teacherDefinition, config_id: 'teacher-algorithm', runtime: 'yuxi', name: '算法老师', system_prompt: '', yuxi: { agent_slug: 'algorithm', agent_version: 4 } } },
|
|
|
|
|
{ teacher_id: 'teacher-algorithm', version: 9, is_default: false, definition: { ...teacherDefinition, config_id: 'teacher-algorithm', runtime: 'yuxi', name: '朋友', system_prompt: '', yuxi: { agent_slug: 'algorithm', agent_version: 4 } } },
|
|
|
|
|
];
|
|
|
|
|
const friendDefinition = {...teacherDefinition,teacher_id:'coding-friend',name:'小麦',welcome_message:'一起看看你的作品',suggested_questions:['想听听你对作品的第一印象']};
|
|
|
|
|
const consultationTopics: Record<string, Record<string, unknown>> = {};
|
|
|
|
|
ipcMain.removeHandler('hostapi:fetch');
|
|
|
|
|
ipcMain.handle('hostapi:fetch', async (
|
|
|
|
|
@@ -529,29 +528,28 @@ async function installCodingFirstChatHost(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path.endsWith('/teacher-check-in') && method === 'POST') {
|
|
|
|
|
const current = consultationTopics.teacher ?? {schemaVersion:1,revision:0,id:'teacher-topic',accountId:'e2e',projectId:project.id,role:'teacher',sourceConversationId:'project',definition:teacherDefinition,version:1,createdAt:now,updatedAt:now,requests:[]};
|
|
|
|
|
consultationTopics.teacher = {...current,revision:Number(current.revision)+1,requests:[...(current.requests as unknown[]),{id:body!.requestId,intent:'check-in',text:'',references:[],sourceConversationId:body!.sourceConversationId,createdAt:now,sourceCursor:{workerGeneration:1,seq:1},sourceCapturedAt:now,includedSourceMessageIds:[],omittedMessages:0,status:'completed',response:'你刚才想加排行榜,我们一起想想比什么更有意思?'}]};
|
|
|
|
|
return respond({topic:consultationTopics.teacher});
|
|
|
|
|
const current = consultationTopics.agent ?? {schemaVersion:1,revision:0,id:'agent-topic',accountId:'e2e',projectId:project.id,sourceConversationId:'project',definition:teacherDefinition,version:1,createdAt:now,updatedAt:now,requests:[]};
|
|
|
|
|
consultationTopics.agent = {...current,revision:Number(current.revision)+1,requests:[...(current.requests as unknown[]),{id:body!.requestId,intent:'check-in',text:'',references:[],sourceConversationId:body!.sourceConversationId,createdAt:now,sourceCursor:{workerGeneration:1,seq:1},sourceCapturedAt:now,includedSourceMessageIds:[],omittedMessages:0,status:'completed',response:'你刚才想加排行榜,我们一起想想比什么更有意思?'}]};
|
|
|
|
|
return respond({topic:consultationTopics.agent});
|
|
|
|
|
}
|
|
|
|
|
if (path === '/api/coding/teacher/teachers') return respond({ items: teachers });
|
|
|
|
|
if (path === '/api/coding/teacher/config') return respond({enabled:true,revision:1,published_version:1,definition:teacherDefinition});
|
|
|
|
|
if (path === '/api/coding/friend/config') return respond({enabled:true,revision:1,published_version:1,definition:friendDefinition});
|
|
|
|
|
const consultationRole = path.match(/\/(teacher|friend)-topics(?:\/|$)/)?.[1];
|
|
|
|
|
if (consultationRole) {
|
|
|
|
|
const topicBase = `/${consultationRole}-topics`;
|
|
|
|
|
const topicId = `${consultationRole}-topic`;
|
|
|
|
|
const currentTopic = consultationTopics[consultationRole];
|
|
|
|
|
if (path.endsWith(topicBase) && method === 'GET') return respond({items:currentTopic?[{id:currentTopic.id,title:consultationRole==='friend'?'朋友话题':'老师话题'}]:[],lastSelectedTopicId:currentTopic?.id??null});
|
|
|
|
|
const consultationPath = path.match(/\/(agent|teacher)-topics(?:\/|$)/)?.[1];
|
|
|
|
|
if (consultationPath) {
|
|
|
|
|
const topicBase = `/${consultationPath}-topics`;
|
|
|
|
|
const topicId = `${consultationPath}-topic`;
|
|
|
|
|
const currentTopic = consultationTopics[consultationPath];
|
|
|
|
|
if (path.endsWith(topicBase) && method === 'GET') return respond({items:currentTopic?[{id:currentTopic.id,title:'智能体话题'}]:[],lastSelectedTopicId:currentTopic?.id??null});
|
|
|
|
|
if (path.endsWith(topicBase) && method === 'POST') {
|
|
|
|
|
const selected = teachers.find(item => item.version === body?.teacherVersion) ?? teachers[0];
|
|
|
|
|
consultationTopics[consultationRole]={schemaVersion:1,revision:0,id:topicId,accountId:'e2e',projectId:project.id,role:consultationRole,sourceConversationId:conversation.id,definition:consultationRole==='friend'?friendDefinition:selected.definition,version:consultationRole==='friend'?1:selected.version,createdAt:now,updatedAt:now,requests:[]};
|
|
|
|
|
return respond(consultationTopics[consultationRole],201);
|
|
|
|
|
consultationTopics[consultationPath]={schemaVersion:1,revision:0,id:topicId,accountId:'e2e',projectId:project.id,sourceConversationId:conversation.id,definition:selected.definition,version:selected.version,createdAt:now,updatedAt:now,requests:[]};
|
|
|
|
|
return respond(consultationTopics[consultationPath],201);
|
|
|
|
|
}
|
|
|
|
|
if (path.endsWith(`${topicBase}/${topicId}/messages`) && method === 'POST') {
|
|
|
|
|
const suggestionRequest = body!.intent === 'suggestions';
|
|
|
|
|
const teacherResponse = suggestionRequest ? '我们可以从你最近试过的地方聊起。' : body!.intent === 'guided-help' ? '你最近做的哪一步,让你停下来想了一会儿?' : '先理解状态如何随点击变化,再修改代码。';
|
|
|
|
|
consultationTopics[consultationRole]={...currentTopic,revision:Number(currentTopic?.revision??0)+1,requests:[...(currentTopic?.requests as unknown[]??[]),{id:body!.requestId,text:body!.text,intent:body!.intent,references:body!.references??[],createdAt:now,sourceCursor:{workerGeneration:1,seq:1},sourceCapturedAt:now,includedSourceMessageIds:[],omittedMessages:0,truncatedMessages:1,status:'completed',response:consultationRole==='friend'?'听你的描述,我有点好奇该从哪里开始玩。':teacherResponse,...(suggestionRequest?{suggestedQuestions:['怎样观察别人玩游戏?','我该先试哪个想法?']}:{})}]};
|
|
|
|
|
return respond(consultationTopics[consultationRole],202);
|
|
|
|
|
consultationTopics[consultationPath]={...currentTopic,revision:Number(currentTopic?.revision??0)+1,requests:[...(currentTopic?.requests as unknown[]??[]),{id:body!.requestId,text:body!.text,intent:body!.intent,references:body!.references??[],createdAt:now,sourceCursor:{workerGeneration:1,seq:1},sourceCapturedAt:now,includedSourceMessageIds:[],omittedMessages:0,truncatedMessages:1,status:'completed',response:teacherResponse,...(suggestionRequest?{suggestedQuestions:['怎样观察别人玩游戏?','我该先试哪个想法?']}:{})}]};
|
|
|
|
|
return respond(consultationTopics[consultationPath],202);
|
|
|
|
|
}
|
|
|
|
|
if (path.endsWith(`${topicBase}/${topicId}`)) return respond(currentTopic);
|
|
|
|
|
}
|
|
|
|
|
@@ -1780,67 +1778,57 @@ test('project consultations preserve student drafts and switch between work and
|
|
|
|
|
await expect(composer).toBeHidden();
|
|
|
|
|
await page.getByRole('tab',{name:'操作对话',exact:true}).click();
|
|
|
|
|
await expect(composer).toHaveValue('保留我的草稿');
|
|
|
|
|
await page.getByRole('button',{name:'老师',exact:true}).click();
|
|
|
|
|
await page.getByRole('button',{name:'智能体',exact:true}).click();
|
|
|
|
|
const teacher=page.getByTestId('teacher-chat-panel');
|
|
|
|
|
await expect(page.getByRole('tab',{name:'作品',exact:true})).toHaveAttribute('aria-selected','true');
|
|
|
|
|
await expect(page.getByRole('tabpanel',{name:'作品',exact:true})).toBeVisible();
|
|
|
|
|
await expect(composer).toBeHidden();
|
|
|
|
|
await expect(composer).toHaveValue('保留我的草稿');
|
|
|
|
|
await expect(teacher.getByText('有问题,随时来找我')).toBeVisible();
|
|
|
|
|
await teacher.getByRole('combobox', { name: '新话题使用的老师' }).selectOption('9');
|
|
|
|
|
await expect(teacher.getByText('一起理解代码')).toBeVisible();
|
|
|
|
|
await teacher.getByRole('combobox', { name: '新话题使用的智能体' }).selectOption('9');
|
|
|
|
|
await expect(page.locator('#coding-consultation-dock')).toHaveCSS('width', '508px');
|
|
|
|
|
await expect(teacher.getByTestId('consultation-composer-actions').getByRole('button', { name: '老师帮我看看', exact: true })).toBeVisible();
|
|
|
|
|
await expect(teacher.getByTestId('consultation-composer-actions').getByRole('button', { name: '帮我看看', exact: true })).toBeVisible();
|
|
|
|
|
await expect(teacher.getByText('把你的困惑说出来就好')).toHaveCount(0);
|
|
|
|
|
await expect(teacher.getByText('当前项目',{exact:true})).toHaveCount(0);
|
|
|
|
|
await expect(teacher.getByRole('button',{name:'解释当前代码',exact:true})).toHaveCount(0);
|
|
|
|
|
await expect(teacher.getByRole('button',{name:'解释当前代码',exact:true})).toBeVisible();
|
|
|
|
|
expect((await readState(electronApp)).captured.filter(item=>item.path.endsWith('/messages')&&item.method==='POST')).toHaveLength(0);
|
|
|
|
|
await teacher.getByRole('textbox',{name:'向老师提问'}).fill('还没说完的困惑');
|
|
|
|
|
await teacher.getByRole('button',{name:'老师帮我看看',exact:true}).click();
|
|
|
|
|
await teacher.getByRole('textbox',{name:'向智能体提问'}).fill('还没说完的困惑');
|
|
|
|
|
await teacher.getByRole('button',{name:'帮我看看',exact:true}).click();
|
|
|
|
|
await expect(teacher.getByText('我们可以从你最近试过的地方聊起。')).toBeVisible();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向老师提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向智能体提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
expect((await readState(electronApp)).captured.filter(item=>item.path.endsWith('/messages')&&item.method==='POST')).toHaveLength(1);
|
|
|
|
|
await teacher.getByRole('button',{name:'怎样观察别人玩游戏?',exact:true}).click();
|
|
|
|
|
await expect(teacher.getByText('先理解状态如何随点击变化,再修改代码。')).toBeVisible();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向老师提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向智能体提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await expect(teacher.getByRole('button',{name:'带回主会话草稿'})).toHaveCount(0);
|
|
|
|
|
await expect(teacher.getByRole('button',{name:'我去试一试'})).toHaveCount(0);
|
|
|
|
|
await expect(teacher.getByRole('button',{name:'复制',exact:true})).toHaveCount(0);
|
|
|
|
|
await expect(composer).toHaveValue('保留我的草稿');
|
|
|
|
|
await teacher.getByRole('button',{name:'我也说不清,你带我看看',exact:true}).click();
|
|
|
|
|
await expect(teacher.getByText('你最近做的哪一步,让你停下来想了一会儿?')).toBeVisible();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向老师提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向智能体提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await page.getByRole('tab',{name:'操作对话',exact:true}).click();
|
|
|
|
|
await expect(composer).toBeVisible();
|
|
|
|
|
await page.getByRole('button',{name:'朋友',exact:true}).click();
|
|
|
|
|
await expect(page.getByRole('tab',{name:'操作对话',exact:true})).toHaveAttribute('aria-selected','true');
|
|
|
|
|
await expect(teacher).toHaveAttribute('data-role','friend');
|
|
|
|
|
await expect(teacher.getByText('一起看看你的作品')).toBeVisible();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向朋友提问'})).toHaveValue('');
|
|
|
|
|
await expect(teacher.getByText('先理解状态如何随点击变化,再修改代码。')).toHaveCount(0);
|
|
|
|
|
await teacher.getByRole('textbox',{name:'向朋友提问'}).fill('你觉得哪里有意思?');
|
|
|
|
|
await teacher.getByRole('button',{name:'关闭朋友'}).click();
|
|
|
|
|
await expect(teacher).toHaveCount(0);
|
|
|
|
|
await page.getByRole('button',{name:'朋友',exact:true}).click();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向朋友提问'})).toHaveValue('你觉得哪里有意思?');
|
|
|
|
|
await page.getByRole('button',{name:'老师',exact:true}).click();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向老师提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await expect(page.getByRole('tab',{name:'作品',exact:true})).toHaveAttribute('aria-selected','true');
|
|
|
|
|
await teacher.getByRole('button',{name:'关闭智能体'}).click();
|
|
|
|
|
await expect(page.getByRole('button',{name:'朋友',exact:true})).toHaveCount(0);
|
|
|
|
|
await page.getByRole('button',{name:'智能体',exact:true}).click();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向智能体提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await expect(teacher.getByText('先理解状态如何随点击变化,再修改代码。')).toBeVisible();
|
|
|
|
|
const requests=(await readState(electronApp)).captured;
|
|
|
|
|
expect(requests.filter(item=>item.path.endsWith('/prompt')&&item.method==='POST')).toHaveLength(0);
|
|
|
|
|
const consultationRequests=requests.filter(item=>item.path.endsWith('/messages')&&item.method==='POST');
|
|
|
|
|
expect(consultationRequests).toHaveLength(3);
|
|
|
|
|
expect(requests.find(item => item.path.endsWith('/teacher-topics') && item.method === 'POST')?.body?.teacherVersion).toBe(9);
|
|
|
|
|
await expect(teacher.getByRole('heading', { name: '算法老师', exact: true })).toBeVisible();
|
|
|
|
|
expect(consultationRequests[0].body).toMatchObject({intent:'suggestions',text:'老师帮我看看'});
|
|
|
|
|
expect(requests.find(item => item.path.endsWith('/agent-topics') && item.method === 'POST')?.body?.teacherVersion).toBe(9);
|
|
|
|
|
await expect(teacher.getByRole('heading', { name: '朋友', exact: true })).toBeVisible();
|
|
|
|
|
expect(consultationRequests[0].body).toMatchObject({intent:'suggestions',text:'帮我看看'});
|
|
|
|
|
expect(consultationRequests[1].body).toMatchObject({text:'怎样观察别人玩游戏?'});
|
|
|
|
|
expect(consultationRequests[1].body?.intent).toBeUndefined();
|
|
|
|
|
expect(consultationRequests[2].body).toMatchObject({intent:'guided-help',text:'我也说不清,你带我看看'});
|
|
|
|
|
await page.screenshot({path:test.info().outputPath('project-teacher-side-chat.png')});
|
|
|
|
|
await teacher.getByRole('button',{name:'关闭老师'}).click();await expect(teacher).toHaveCount(0);
|
|
|
|
|
await page.getByRole('button',{name:'老师',exact:true}).click();
|
|
|
|
|
await teacher.getByRole('button',{name:'关闭智能体'}).click();await expect(teacher).toHaveCount(0);
|
|
|
|
|
await page.getByRole('button',{name:'智能体',exact:true}).click();
|
|
|
|
|
await expect(page.getByText('先理解状态如何随点击变化,再修改代码。',{exact:true})).toBeVisible();
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向老师提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
await expect(teacher.getByRole('textbox',{name:'向智能体提问'})).toHaveValue('还没说完的困惑');
|
|
|
|
|
} finally {await releaseSnapshot(electronApp);}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -1901,11 +1889,11 @@ test('teacher stays in the top-right header and proactively checks in without ta
|
|
|
|
|
await page.evaluate(() => { window.location.hash = '/chat'; });
|
|
|
|
|
const header = page.getByTestId('coding-conversation-header');
|
|
|
|
|
const companion = header.getByTestId('teacher-companion');
|
|
|
|
|
const teacherButton = companion.getByRole('button', { name: '老师', exact: true });
|
|
|
|
|
await expect(teacherButton).toHaveText('代码老师');
|
|
|
|
|
const teacherButton = companion.getByRole('button', { name: '智能体', exact: true });
|
|
|
|
|
await expect(teacherButton).toHaveText('代码智能体');
|
|
|
|
|
await expect(teacherButton).toBeVisible();
|
|
|
|
|
await expect(teacherButton.locator('img')).toHaveAttribute('src', /teacher-pixel/);
|
|
|
|
|
await expect(page.getByRole('checkbox', { name: '老师偶尔来看看' })).toHaveCount(0);
|
|
|
|
|
await expect(teacherButton.locator('img')).toHaveAttribute('src', /avatar-01/);
|
|
|
|
|
await expect(page.getByRole('checkbox', { name: '智能体偶尔来看看' })).toHaveCount(0);
|
|
|
|
|
await expect(page.getByTestId('teacher-companion')).toHaveCount(1);
|
|
|
|
|
const invitation = companion.getByTestId('teacher-invitation');
|
|
|
|
|
await expect(invitation).toHaveAttribute('data-bubble-kind', 'welcome');
|
|
|
|
|
@@ -1932,10 +1920,10 @@ test('teacher stays in the top-right header and proactively checks in without ta
|
|
|
|
|
await expect(invitation).toHaveAttribute('data-bubble-kind', 'check-in');
|
|
|
|
|
await expect(invitation).toBeInViewport({ ratio: 1 });
|
|
|
|
|
const presence = companion.getByTestId('teacher-presence');
|
|
|
|
|
await expect(presence.getByRole('button', { name: '打开这条老师消息' })).toBeVisible();
|
|
|
|
|
await expect(presence.getByRole('button', { name: '打开这条老师消息' }).locator('img')).toHaveAttribute('src', await teacherButton.locator('img').getAttribute('src') as string);
|
|
|
|
|
await expect(presence.getByRole('button', { name: '打开这条智能体消息' })).toBeVisible();
|
|
|
|
|
await expect(presence.getByRole('button', { name: '打开这条智能体消息' }).locator('img')).toHaveAttribute('src', await teacherButton.locator('img').getAttribute('src') as string);
|
|
|
|
|
const [invitationBounds, companionBounds, avatarBounds, presenceBounds, speakerBounds] = await Promise.all([
|
|
|
|
|
invitation.boundingBox(), companion.boundingBox(), teacherButton.locator('img').boundingBox(), presence.boundingBox(), presence.getByRole('button', { name: '打开这条老师消息' }).boundingBox(),
|
|
|
|
|
invitation.boundingBox(), companion.boundingBox(), teacherButton.locator('img').boundingBox(), presence.boundingBox(), presence.getByRole('button', { name: '打开这条智能体消息' }).boundingBox(),
|
|
|
|
|
]);
|
|
|
|
|
expect(invitationBounds).not.toBeNull();
|
|
|
|
|
expect(companionBounds).not.toBeNull();
|
|
|
|
|
@@ -1964,7 +1952,7 @@ test('teacher stays in the top-right header and proactively checks in without ta
|
|
|
|
|
await expect(composer).toHaveValue('我还在写自己的想法');
|
|
|
|
|
await page.clock.fastForward(300_000);
|
|
|
|
|
expect((await readState(electronApp)).captured.filter((item) => item.path.endsWith('/teacher-check-in'))).toHaveLength(1);
|
|
|
|
|
await invitation.getByRole('button', { name: '和老师聊聊' }).click();
|
|
|
|
|
await invitation.getByRole('button', { name: '和智能体聊聊' }).click();
|
|
|
|
|
await expect(invitation).toHaveCount(0);
|
|
|
|
|
await expect(presence).toHaveCount(0);
|
|
|
|
|
await expect(teacherButton).toBeVisible();
|
|
|
|
|
@@ -1973,12 +1961,12 @@ test('teacher stays in the top-right header and proactively checks in without ta
|
|
|
|
|
const teacher = page.getByTestId('teacher-chat-panel');
|
|
|
|
|
await expect(teacher.getByText('你刚才想加排行榜,我们一起想想比什么更有意思?')).toBeVisible();
|
|
|
|
|
await expect(teacher.locator('.consultation-user')).toHaveCount(0);
|
|
|
|
|
const teacherInput = teacher.getByRole('textbox', { name: '向老师提问' });
|
|
|
|
|
const teacherInput = teacher.getByRole('textbox', { name: '向智能体提问' });
|
|
|
|
|
await expect(teacherInput).toBeFocused();
|
|
|
|
|
await teacherInput.fill('我想比谁更了解自己的宠物');
|
|
|
|
|
await teacher.getByRole('button', { name: '关闭老师' }).click();
|
|
|
|
|
await expect(page.getByRole('button', { name: '老师', exact: true })).toBeFocused();
|
|
|
|
|
await page.getByRole('button', { name: '老师', exact: true }).click();
|
|
|
|
|
await teacher.getByRole('button', { name: '关闭智能体' }).click();
|
|
|
|
|
await expect(page.getByRole('button', { name: '智能体', exact: true })).toBeFocused();
|
|
|
|
|
await page.getByRole('button', { name: '智能体', exact: true }).click();
|
|
|
|
|
await expect(teacherInput).toHaveValue('我想比谁更了解自己的宠物');
|
|
|
|
|
await teacherInput.fill('');
|
|
|
|
|
// Let the cleared draft propagate through the parent before advancing the check-in timer.
|
|
|
|
|
@@ -1987,7 +1975,7 @@ test('teacher stays in the top-right header and proactively checks in without ta
|
|
|
|
|
await page.clock.runFor(15_000);
|
|
|
|
|
await expect(invitation).toBeVisible();
|
|
|
|
|
await expect(teacher).toBeVisible();
|
|
|
|
|
await expect(invitation.getByRole('button', { name: '看看老师说的' })).toBeVisible();
|
|
|
|
|
await expect(invitation.getByRole('button', { name: '看看智能体说的' })).toBeVisible();
|
|
|
|
|
// The message sits over the separate teacher pane and must not push Work down.
|
|
|
|
|
await expect.poll(async () => (await workPanel.boundingBox())!.y - (await workPane.boundingBox())!.y).toBe(20);
|
|
|
|
|
await page.screenshot({ path: test.info().outputPath('teacher-open-proactive-invitation.png') });
|
|
|
|
|
|