{"record":{"id":"1483ddeefe5a32de","repo":"jackwener/OpenCLI","slug":"ke-no-user-name-dom-anchor-anonymous-or-ssr-fail","errorCode":null,"errorMessage":"Ke no user-name DOM anchor — anonymous or SSR failed","messagePattern":"Ke no user-name DOM anchor — anonymous or SSR failed","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/ke/auth.js","lineNumber":31,"sourceCode":"  await page.goto('https://www.ke.com/');\n  await page.wait(2);\n  const probe = await page.evaluate(`\n    (() => {\n      const loginBtn = document.querySelector('.btn-login, a[class*=actLoginBtn], .login-btn');\n      if (loginBtn && /登录|登陆/.test(loginBtn.innerText || '')) {\n        return { kind: 'auth', detail: 'Ke shows 登录 button — anonymous session' };\n      }\n      // 2026-08 贝壳新版 SSR：用户名在 .typeShowUser（脱敏手机号如 15****93），\n      // 旧锚点 .userNick/.user-name/.myInfo 已下线，故把 .typeShowUser 提到最前。\n      const el = document.querySelector('.typeShowUser a span, .typeShowUser a, .userNick, .user-name, .myInfo a, [class*=userNick]');\n      const username = (el?.innerText || '').trim();\n      if (!username) {\n        return { kind: 'auth', detail: 'Ke no user-name DOM anchor — anonymous or SSR failed' };\n      }\n      return { ok: true, username };\n    })()\n  `);\n  if (probe?.kind === 'auth') throw new AuthRequiredError('ke.com', probe.detail);\n  if (!probe?.ok) throw new CommandExecutionError(`Unexpected Ke probe: ${JSON.stringify(probe)}`);\n  return { username: probe.username };\n}\n\nregisterSiteAuthCommands({\n  site: 'ke',\n  domain: 'ke.com',\n  loginUrl: 'https://clogin.ke.com/login/?service=https%3A%2F%2Fwww.ke.com',\n  columns: ['username'],\n  verify: verifyKeIdentity,\n  poll: async (page) => {\n    if (!await hasKeSessionCookie(page)) {\n      throw new AuthRequiredError('ke.com', 'Waiting for Ke lianjia_token cookie');\n    }\n    return verifyKeIdentity(page);\n  },\n});\n","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ke/auth.js#L13-L49","documentation":"After confirming the `lianjia_token` cookie, verifyKeIdentity injects a DOM probe looking for the logged-in user-name anchor (.typeShowUser a, .userNick, .user-name, .myInfo a). If no anchor yields a non-empty username it throws AuthRequiredError — either the session is effectively anonymous or the server-rendered header failed to render, so identity cannot be confirmed.","triggerScenarios":"Cookie exists but the page shows a 登录-free header without any username element; ke.com SSR/layout change renames or removes the CSS anchors; page.goto to https://www.ke.com/ returned an error/blank page; slow render so the header wasn't in DOM after the 2s wait.","commonSituations":"Beike frontend redeploy changes header markup (as happened with the 2026-08 .typeShowUser SSR migration); network hiccup yields partial SSR; logged-in cookie but server treats the session as anonymous.","solutions":["Re-run the command — transient SSR failure often resolves on retry","Re-login via the ke auth login flow to refresh the session","Inspect https://www.ke.com/ in the browser: if logged in but no username element, the adapter's DOM selectors are stale — update them in clis/ke/auth.js","Increase the wait (page.wait) if the header renders late"],"exampleFix":"// before\nconst el = document.querySelector('.userNick, .user-name, .myInfo a');\n// after (match current SSR markup)\nconst el = document.querySelector('.typeShowUser a span, .typeShowUser a, .userNick, .user-name, .myInfo a, [class*=userNick]');","handlingStrategy":"try-catch","validationCode":"const probe = await page.evaluate(`(() => {\n  const el = document.querySelector('.typeShowUser a span, .typeShowUser a, .userNick, .user-name, .myInfo a, [class*=userNick]');\n  return el ? (el.innerText || '').trim() : '';\n})()`);\nif (!probe) console.warn('username anchor not found — SSR may have failed; retry or re-login');","typeGuard":"function hasUsernameAnchor(doc = document) {\n  const el = doc.querySelector('.typeShowUser a span, .typeShowUser a, .userNick, .user-name, .myInfo a, [class*=userNick]');\n  return typeof el?.innerText === 'string' && el.innerText.trim().length > 0;\n}","tryCatchPattern":"try {\n  const { username } = await keVerify(page);\n} catch (e) {\n  if (/no user-name DOM anchor/.test(e.message)) {\n    await page.reload(); await page.wait(3);   // transient SSR failure: retry once\n    return keVerify(page);\n  }\n  throw e;\n}","preventionTips":["Retry once with a reload before surfacing auth errors — SSR is often flaky","After ke.com frontend deploys, smoke-test the verify command to catch stale selectors","Keep multiple selector candidates (as the adapter does) and update promptly on layout changes","Wait for a header element selector before probing instead of a fixed sleep"],"tags":["auth","dom-scraping","selector","ke"],"backgroundTag":"dom-selector-stale","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}