Protractor执行Promise后无法定位同一元素的问题求助
嘿,我来帮你分析下这个问题——你能成功获取span的文本却点击失败,核心大概率是异步操作顺序或者Angular异步任务未完全处理导致的,结合你给出的代码和错误信息,我整理了几个可行的解决思路:
先理清楚你的场景
你登录后成功验证了用户span的文本,但点击同一元素时触发了超时错误,提示Angular异步任务没完成。你试过手动等待和waitForAngular()但没见效,那咱们从根源入手:
你的代码片段
HTML结构
<span class="span-user" id="spanuser"> {{user?.login}}</span>
测试代码
describe('Login OK with correct pass', () => { it('should login successfully with admin account', () => { // 登录流程 username.clear(); username.sendKeys('admin'); password.clear(); password.sendKeys('admin'); element(by.css('button[type=submit]')).click(); // 验证span文本 const expect2 = /admin/; const spanuser = element(by.css('span#spanuser')); spanuser.getText().then((value) => { console.log('inside'); console.log(value ? value : 'no value'); expect(value).toMatch(expect2); }); // 尝试点击span spanuser.click().then(() => { console.log('It has been pressed!'); }); }); });
错误提示
Failed: Timed out waiting for asynchronous Angular tasks to finish after 5 seconds. This may be because the current page is not an Angular application.
While waiting for element with locator - Locator: By(css selector, span#spanuser)
可能的原因&解决办法
1. 异步操作顺序混乱(最可能的原因)
Protractor的所有API都是异步的,你写的getText()和click()会被并行执行,而不是按代码顺序依次完成。也就是说,在getText()的回调还没跑完时,click()可能已经开始执行——这时候登录后的Angular异步任务(比如用户数据加载、视图渲染)还没完成,自然会触发超时。
解决办法:用async/await强制顺序执行
这是现在Protractor推荐的写法,代码更清晰,还能避免回调地狱:
describe('Login OK with correct pass', () => { // 给it函数加上async it('should login successfully with admin account', async () => { // 登录流程都加await await username.clear(); await username.sendKeys('admin'); await password.clear(); await password.sendKeys('admin'); await element(by.css('button[type=submit]')).click(); // 验证文本 const expect2 = /admin/; const spanuser = element(by.css('span#spanuser')); const value = await spanuser.getText(); console.log('inside'); console.log(value ? value : 'no value'); expect(value).toMatch(expect2); // 等文本验证完成后,再执行点击 await spanuser.click(); console.log('It has been pressed!'); }); });
2. 元素还没处于可点击状态
虽然getText()能拿到内容,但元素可能还没完全渲染好(比如被加载遮罩层挡住、Angular还在做变更检测),导致点击时Protractor认为元素不可交互,进而等待超时。
解决办法:用ExpectedConditions等待元素可点击
Protractor内置了ExpectedConditions工具类,可以帮你等待元素满足特定状态:
// 先引入ExpectedConditions const EC = protractor.ExpectedConditions; describe('Login OK with correct pass', () => { it('should login successfully with admin account', async () => { // ... 登录流程省略 ... const spanuser = element(by.css('span#spanuser')); // 先等元素可点击,超时时间设为10秒 await browser.wait(EC.elementToBeClickable(spanuser), 10000); // 再做文本验证和点击 const value = await spanuser.getText(); expect(value).toMatch(/admin/); await spanuser.click(); console.log('It has been pressed!'); }); });
3. 页面存在非Angular异步任务
如果登录后页面有第三方请求(比如非Angular的API调用)或者用了原生setTimeout,这些任务不会被Protractor的waitForAngular()跟踪,导致Protractor一直等待Angular任务完成,最终超时。
解决办法:临时禁用Angular等待
如果确定页面有非Angular异步操作,可以暂时关掉Protractor的Angular等待机制:
describe('Login OK with correct pass', () => { it('should login successfully with admin account', async () => { // ... 登录流程省略 ... const spanuser = element(by.css('span#spanuser')); // 临时禁用Angular等待 await browser.waitForAngularEnabled(false); // 等待元素出现 await browser.wait(EC.presenceOf(spanuser), 10000); const value = await spanuser.getText(); expect(value).toMatch(/admin/); // 点击后如果后续还要操作Angular页面,记得重新启用 await spanuser.click(); await browser.waitForAngularEnabled(true); console.log('It has been pressed!'); }); });
4. 定位器稳定性优化(可选)
虽然你用了span#spanuser已经很明确,但有时候动态渲染的页面可能会有元素状态变化,你可以试试结合class和id的定位方式,让定位器更稳定:
const spanuser = element(by.css('span.span-user#spanuser'));
总结
优先试试async/await重构代码,这能从根本上解决异步顺序的问题;如果还是不行,再结合ExpectedConditions等待元素可点击,或者临时禁用Angular等待。这些方法应该能解决你遇到的超时问题。
内容的提问来源于stack exchange,提问作者Rohr Facu

