# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: market-settings-qa.spec.js >> marketplace and settings QA >> responsive layouts have no page overflow or visible small targets
- Location: ../../../../../../tmp/beaver-market-settings-qa/market-settings-qa.spec.js:241:3

# Error details

```
TimeoutError: locator.click: Timeout 8000ms exceeded.
Call log:
  - waiting for getByRole('button', { name: /Telegram/ })

```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - generic [ref=e2]:
    - banner [ref=e3]:
      - generic [ref=e5]:
        - button "Open navigation" [ref=e7] [cursor=pointer]:
          - img [ref=e8]
        - generic [ref=e10]:
          - generic [ref=e11]:
            - img [ref=e12]
            - button "ZH" [ref=e16] [cursor=pointer]
            - button "EN" [ref=e17] [cursor=pointer]
          - button "Open account menu" [ref=e18] [cursor=pointer]:
            - generic [ref=e20]: U
    - main [ref=e21]:
      - generic [ref=e22]:
        - generic [ref=e23]:
          - generic [ref=e24]:
            - heading "Settings" [level=1] [ref=e25]
            - paragraph [ref=e26]: Manage models, tools, integrations, and instance runtime status. Tasks and notifications stay in their own pages.
          - button "Refresh" [ref=e27] [cursor=pointer]:
            - img [ref=e28]
            - text: Refresh
        - generic [ref=e33]:
          - heading "Instance runtime" [level=3] [ref=e35]:
            - img [ref=e36]
            - text: Instance runtime
          - generic [ref=e39]:
            - generic [ref=e40]:
              - generic [ref=e41]:
                - paragraph [ref=e42]: Runtime and debugging
                - paragraph [ref=e43]: Inspect per-chat runtime logs and current instance status.
              - generic [ref=e44]:
                - link "Runtime Logs" [ref=e45] [cursor=pointer]:
                  - /url: /logs
                  - img [ref=e46]
                  - text: Runtime Logs
                - button "Restart instance" [ref=e49] [cursor=pointer]:
                  - img [ref=e50]
                  - text: Restart instance
            - generic [ref=e55]:
              - generic [ref=e56]:
                - generic [ref=e57]: Config file
                - code [ref=e59]: /home/ivan/very-long-ui-token-0123456789abcdefghijklmnopqrstuvwxyz-0123456789/config/beaver.yml
              - generic [ref=e60]:
                - generic [ref=e61]: Workspace
                - code [ref=e63]: /home/ivan/very-long-ui-token-0123456789abcdefghijklmnopqrstuvwxyz-0123456789/workspace
        - generic [ref=e64]:
          - heading "Agent configuration" [level=3] [ref=e66]:
            - img [ref=e67]
            - text: Agent configuration
          - generic [ref=e70]:
            - generic [ref=e71]:
              - generic [ref=e72]: Model
              - code [ref=e74]: qwen-very-long-ui-token-0123456789abcdefghijklmnopqrstuvwxyz-0123456789
            - generic [ref=e75]:
              - generic [ref=e76]:
                - generic [ref=e77]: Max tokens
                - textbox "Max tokens" [ref=e78]:
                  - /placeholder: Model default
                  - text: "4096"
              - generic [ref=e79]:
                - generic [ref=e80]: Temperature
                - textbox "Temperature" [ref=e81]: "0.2"
              - generic [ref=e82]:
                - generic [ref=e83]: Max tool iterations
                - textbox "Max tool iterations" [ref=e84]: "30"
            - button "Save agent config" [ref=e86] [cursor=pointer]
        - generic [ref=e87]:
          - heading "Providers" [level=3] [ref=e89]:
            - img [ref=e90]
            - text: Providers
          - generic [ref=e95]:
            - button "OpenAI very-long-ui-token-0123456789abcdefghijklmnopqrstuvwxyz-0123456789 Current default sk-********" [ref=e96] [cursor=pointer]:
              - generic [ref=e97]:
                - generic [ref=e98]:
                  - img [ref=e99]
                  - generic [ref=e102]: OpenAI very-long-ui-token-0123456789abcdefghijklmnopqrstuvwxyz-0123456789
                - generic [ref=e103]: Current default
                - generic [ref=e104]: sk-********
              - img [ref=e105]
            - button "DeepSeek Click to configure" [ref=e108] [cursor=pointer]:
              - generic [ref=e109]:
                - generic [ref=e110]:
                  - img [ref=e111]
                  - generic [ref=e115]: DeepSeek
                - generic [ref=e116]: Click to configure
              - img [ref=e117]
        - generic [ref=e120]:
          - heading "Channels" [level=3] [ref=e122]:
            - img [ref=e123]
            - text: Channels
          - generic [ref=e131]:
            - button "Weixin QR Connect" [ref=e132] [cursor=pointer]:
              - generic [ref=e133]:
                - generic [ref=e134]:
                  - img [ref=e135]
                  - generic [ref=e141]: Weixin
                - generic [ref=e142]: QR
              - generic [ref=e143]: Connect
            - button "Feishu/Lark plugin Connect" [ref=e144] [cursor=pointer]:
              - generic [ref=e145]:
                - generic [ref=e146]:
                  - img [ref=e147]
                  - generic [ref=e153]: Feishu/Lark
                - generic [ref=e154]: plugin
              - generic [ref=e155]: Connect
            - button "Terminal Local terminal connection Connection instructions Guide" [ref=e156] [cursor=pointer]:
              - generic [ref=e157]:
                - generic [ref=e158]:
                  - img [ref=e159]
                  - generic [ref=e165]: Terminal
                - generic [ref=e166]: Local terminal connection
                - generic [ref=e167]: Connection instructions
              - generic [ref=e168]: Guide
  - alert [ref=e169]
```

# Test source

```ts
  161 |     await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([{ connection_id: `conn-${LONG}`, channel_id: `telegram-${LONG}`, kind: 'telegram', mode: 'polling', display_name: 'Telegram', account_id: `bot-${LONG}`, status: 'connected', auth_type: 'token' }]) });
  162 |   });
  163 |   await page.route('**/api/runtime/restart', async (route) => {
  164 |     callLog.push({ method: route.request().method(), url: route.request().url() });
  165 |     await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ ok: true }) });
  166 |   });
  167 | }
  168 | 
  169 | async function collectMetrics(page) {
  170 |   return await page.evaluate(() => {
  171 |     const viewportWidth = window.innerWidth;
  172 |     const bodyWidth = document.body.scrollWidth;
  173 |     const docWidth = document.documentElement.scrollWidth;
  174 |     const overflowElements = Array.from(document.querySelectorAll('body *')).map((el) => {
  175 |       const rect = el.getBoundingClientRect();
  176 |       return { tag: el.tagName, text: (el.textContent || el.getAttribute('aria-label') || '').trim().slice(0, 100), left: rect.left, right: rect.right, width: rect.width, className: String(el.className || '') };
  177 |     }).filter((item) => item.width > 1 && (item.left < -1 || item.right > viewportWidth + 1)).slice(0, 30);
  178 |     const smallTargets = Array.from(document.querySelectorAll('button, a, input:not([type=hidden]), textarea, [role="button"], [role="switch"], [role="combobox"], [role="tab"]')).map((el) => {
  179 |       const rect = el.getBoundingClientRect();
  180 |       const style = window.getComputedStyle(el);
  181 |       return { tag: el.tagName, text: (el.textContent || el.getAttribute('aria-label') || el.getAttribute('title') || '').trim().slice(0, 100), width: Math.round(rect.width), height: Math.round(rect.height), visible: style.visibility !== 'hidden' && style.display !== 'none' && Number(style.opacity) > 0.01 && rect.width > 0 && rect.height > 0 };
  182 |     }).filter((item) => item.visible && (item.width < 44 || item.height < 44)).slice(0, 30);
  183 |     return { viewportWidth, bodyWidth, docWidth, horizontalOverflow: Math.max(bodyWidth, docWidth) > viewportWidth + 1, overflowElements, smallTargets };
  184 |   });
  185 | }
  186 | 
  187 | test.describe('marketplace and settings QA', () => {
  188 |   test('marketplace search detail file install flow works', async ({ page }) => {
  189 |     await page.setViewportSize({ width: 390, height: 844 });
  190 |     await installRoutes(page);
  191 |     await page.goto(`${APP}/marketplace`);
  192 |     await expect(page.getByPlaceholder(/搜索技能|Search skills/)).toBeVisible();
  193 |     await page.getByPlaceholder(/搜索技能|Search skills/).fill('qa');
  194 |     await page.getByRole('button', { name: /搜索|Search/ }).click();
  195 |     await page.getByText(/SkillHub very-long-ui-token/).click();
  196 |     await expect(page.getByRole('heading', { name: /SkillHub very-long-ui-token/ })).toBeVisible();
  197 |     await page.getByRole('tab', { name: /文件|Files/ }).click();
  198 |     await page.getByRole('button', { name: /src\/very-long-ui-token/ }).click();
  199 |     await expect(page.getByText(/src\/very-long-ui-token/)).toBeVisible();
  200 |     await page.getByRole('button', { name: /安装|Install/ }).click();
  201 |     await expect.poll(() => callLog.some((call) => call.url.includes('/install') && call.method === 'POST')).toBe(true);
  202 |     await page.screenshot({ path: path.join(SHOT_DIR, 'market-detail-390x844.png'), fullPage: true });
  203 |   });
  204 | 
  205 |   test('settings agent provider channel and restart flows work', async ({ page }) => {
  206 |     await page.setViewportSize({ width: 390, height: 844 });
  207 |     await installRoutes(page);
  208 |     await page.goto(`${APP}/settings`);
  209 |     await expect(page.getByRole('heading', { name: /配置|Settings/ })).toBeVisible();
  210 |     await page.getByLabel(/温度|Temperature/).fill('0.3');
  211 |     await page.getByRole('button', { name: /保存智能体配置|Save agent config/ }).click();
  212 |     await expect.poll(() => callLog.some((call) => call.url.includes('/api/agent/config') && call.method !== 'GET')).toBe(true);
  213 |     await page.getByRole('button', { name: /OpenAI/ }).click();
  214 |     await expect(page.getByRole('dialog')).toBeVisible();
  215 |     await page.getByLabel(/请求超时|Request timeout/).fill('60');
  216 |     await page.getByRole('button', { name: /保存|Save/ }).click();
  217 |     await expect.poll(() => callLog.some((call) => call.url.includes('/api/providers/openai/config'))).toBe(true);
  218 |     await page.getByRole('button', { name: /Telegram/ }).click();
  219 |     await expect(page.getByRole('dialog')).toBeVisible();
  220 |     await page.getByLabel(/显示名|Display name/).fill(`Telegram ${LONG}`);
  221 |     await page.getByRole('button', { name: /保存通道配置|Save channel config/ }).click();
  222 |     await expect.poll(() => callLog.some((call) => call.url.includes('/api/channels/') && call.method !== 'GET')).toBe(true);
  223 |     await page.screenshot({ path: path.join(SHOT_DIR, 'settings-channel-390x844.png'), fullPage: true });
  224 |     await page.keyboard.press('Escape');
  225 |     await page.getByRole('button', { name: /重启实例|Restart instance/ }).first().click();
  226 |     await expect(page.getByRole('dialog')).toBeVisible();
  227 |     await page.getByRole('button', { name: /^重启$|^Restart$/ }).click();
  228 |     await expect.poll(() => callLog.some((call) => call.url.includes('/api/runtime/restart'))).toBe(true);
  229 |   });
  230 | 
  231 |   test('settings error state is readable', async ({ page }) => {
  232 |     await page.setViewportSize({ width: 390, height: 844 });
  233 |     await installRoutes(page, { statusError: true });
  234 |     await page.goto(`${APP}/settings`);
  235 |     await expect(page.getByText(/QA status error|无法连接|Unable to connect/)).toBeVisible();
  236 |     await page.screenshot({ path: path.join(SHOT_DIR, 'settings-error-390x844.png'), fullPage: true });
  237 |     const metrics = await collectMetrics(page);
  238 |     expect(metrics.horizontalOverflow, JSON.stringify(metrics.overflowElements, null, 2)).toBe(false);
  239 |   });
  240 | 
  241 |   test('responsive layouts have no page overflow or visible small targets', async ({ browser }) => {
  242 |     const results = [];
  243 |     for (const viewport of [
  244 |       { width: 320, height: 568 },
  245 |       { width: 390, height: 844 },
  246 |       { width: 844, height: 390 },
  247 |       { width: 768, height: 1024 },
  248 |       { width: 1365, height: 900 },
  249 |     ]) {
  250 |       const market = await browser.newPage({ viewport });
  251 |       await installRoutes(market);
  252 |       await market.goto(`${APP}/marketplace`);
  253 |       await market.getByText(/SkillHub very-long-ui-token/).click();
  254 |       await market.screenshot({ path: path.join(SHOT_DIR, `market-${viewport.width}x${viewport.height}.png`), fullPage: true });
  255 |       const marketMetrics = await collectMetrics(market);
  256 |       await market.close();
  257 | 
  258 |       const settings = await browser.newPage({ viewport });
  259 |       await installRoutes(settings);
  260 |       await settings.goto(`${APP}/settings`);
> 261 |       await settings.getByRole('button', { name: /Telegram/ }).click();
      |                                                                ^ TimeoutError: locator.click: Timeout 8000ms exceeded.
  262 |       await settings.screenshot({ path: path.join(SHOT_DIR, `settings-${viewport.width}x${viewport.height}.png`), fullPage: true });
  263 |       const settingsMetrics = await collectMetrics(settings);
  264 |       await settings.close();
  265 | 
  266 |       results.push({ viewport, marketMetrics, settingsMetrics });
  267 |       expect(marketMetrics.horizontalOverflow, JSON.stringify(marketMetrics.overflowElements, null, 2)).toBe(false);
  268 |       expect(settingsMetrics.horizontalOverflow, JSON.stringify(settingsMetrics.overflowElements, null, 2)).toBe(false);
  269 |       expect(marketMetrics.smallTargets, JSON.stringify(marketMetrics.smallTargets, null, 2)).toEqual([]);
  270 |       expect(settingsMetrics.smallTargets, JSON.stringify(settingsMetrics.smallTargets, null, 2)).toEqual([]);
  271 |     }
  272 |     fs.writeFileSync(REPORT, JSON.stringify({ results }, null, 2));
  273 |   });
  274 | });
  275 | 
```