Node+puppeteer学习笔记(三)--API问题解决--切换frame和iframe框

终于环境搭建好啦,可以开始快乐的玩耍了,开始记录我遇到的问题了

官方英文版API入口:https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md.
汉化版API入口:https://yq.aliyun.com/articles/607102.
学习笔记入口:https://blog.csdn.net/qupan1993/article/details/85371556.

具体API我就不解释,在前边第一篇中已经给出学习的目录了,可去看下基础的,API实在是太多了,我这边只列出我自己遇到的有问题的API

1、切换单层iframe框架

先给出具体代码:一个登录163邮箱的例子
看一下效果图

Node+puppeteer学习笔记(三)--API问题解决--切换frame和iframe框_第1张图片
代码如下:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless:false});
  const page = await browser.newPage();
  await page.goto('https://mail.163.com/');
  await page.setViewport({width:1000,height:800});
  //切换iframe框代码
  await page.waitFor('#switchAccountLogin');
  await page.click('#switchAccountLogin');//默认是扫二维码的,点击密码登录,调出输入框
  await page.waitFor('[id*="x-URS-iframe"]');//等待我的iframe出现
  const frame = ( await page.frames() )[4];//通过索引得到我的iframe
  await frame.waitFor('.j-inputtext.dlemail');//等待用户名输入框出现
  await frame.type('.j-inputtext.dlemail','12345');//输入账户
  await frame.waitFor('.dlpwd');//等待密码框出现
  await frame.type('.dlpwd','12345');//输入密码
  //等待3秒后退出浏览器
  await page.waitFor(3000);
  await browser.close();
})();

当时学习这个可真是苦恼我了,查了好多资料,看了好多教程都没有解决切换的问题,都只是说使用page.frames()得到所有的frame框返回一个列表来使用,但是并没有什么例子,看的我云里来雾里去的,幸好我有UI自动化的功底,有一些思路,于是自己摸索出来了,话不多说、上分析吧:

1.首先在第9行我加了一个智能等待,等待包含输入账户和密码的iframe框加载出来,有时候网络加载慢这个iframe框还没出来,但是其它的iframe已经加载出来了,而page.frames()语句照样会执行,这样就会找不到我要的iframe了,所以加一下等待
2. 第10行代码我用的是索引的方法直接得到我的iframe,因为没有name的属性不能找到,如果有name的属性可以这样写,如果没有name,返回ID,
原理Node+puppeteer学习笔记(三)--API问题解决--切换frame和iframe框_第2张图片

const frame = await page.frames().find(f => f.name() === 'name');

另外如果使用索引的时候可以这样查看列表有多少个值:

const frame = await page.frames();//得到所有的frame框
console.log(frames.length);//查看得到的frame列表数量

3.第11行我加了一个智能等待,等输入框出现,这个我不知道为什么?如果我不加的话就会报错,找不到输入框,如果加上的话就没问题,如果谁知道怎么回事可以给我说下。
4.后面的12,13,14行都一样了就是正常的等待和输入就好啦。

2、切换多层iframe框架

这个多层的iframe真的很少了,至少我都是见到的单层的,现在已经很少会有多层了,不过少归少并不代表没有,于是自己写了几个html为大家讲解一下吧!
先看下效果图:
Node+puppeteer学习笔记(三)--API问题解决--切换frame和iframe框_第3张图片
首先新建三个HTML文件和脚本文件,放到一个路径下,输入以下内容:
input.html

<!DOCTYPE html>
<html>
<head>
	<title>input</title>
</head>
<body>
	<input type="text" id="input_01">
</body>
</html>

frame.html

<!DOCTYPE html>
<html>
<head>
	<title>frame</title>
</head>
<body>
	<iframe src="input.html" name="mainframe"></iframe>
</body>
</html>

iframe.html

<!DOCTYPE html>
<html>
<head>
	<title>iframe</title>
</head>
<body>
	<iframe src="frame.html" name="leftframe"></iframe>
	<iframe src="frame.html" name="rightframe"></iframe>
	<input type="text" id="input_02">
</body>
</html>

多层切换会有一些麻烦,脚本如下(PS:记得把page.goto()中的地址换成自己电脑的,因为是本地文件,每个地址都不同:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless:false});
  const page = await browser.newPage();
  await page.goto('file:///MAC/Study/27.Puppeteer/case/iframe.html');
  await page.setViewport({width:1000,height:800});
  // 得到第一个iframe框架
  const frame1 = await page.frames().find(f => f.name() === 'leftframe');
  // 得到第一个iframe框架的子框架
  const childframe1 = ( await frame1.childFrames() )[0];
  // 等待输入框出现,输入信息
  await childframe1.waitFor('#input_01');
  await childframe1.type('#input_01','第一次输入:leftframe');
  
  // 得到第二个iframe框架
  const frame2 = await page.frames().find(f => f.name() === 'rightframe');
  // 得到第二个iframe框架的子框架
  const childframe2 = ( await frame2.childFrames() )[0];
  // 等待输入框出现,输入信息
  await childframe2.waitFor('#input_01');
  await childframe2.type('#input_01','第一次输入:rightframe');

  // 在iframe外面的输入框输入文字,直接使用句柄进行操作,不用切换iframe
  await page.waitFor('#input_02');
  await page.type('#input_02','第一次输入:out');

  // 第二次输入
  // 在第一个iframe中清空文本再次输入,不用重新获取frame,直接使用句柄操作
  await page.waitFor(2000);
  await childframe1.waitFor('#input_01');
  await childframe1.$eval('#input_01',input => input.value='第二次输入:leftframe')
  // 在第二个iframe中清空文本再次输入,不用重新获取frame,直接使用句柄操作
  await page.waitFor(2000);
  await childframe2.waitFor('#input_01');
  await childframe2.$eval('#input_01',input => input.value='第二次输入:leftframe')
  // 在iframe最外面的清空文本再次输入,不用重新获取frame,直接使用句柄操作
  await page.waitFor(2000);
  await page.waitFor('#input_02');
  await page.$eval('#input_02',input => input.value='第二次输入:out');

  await page.waitFor(3000);
  await browser.close();
})();

好啦,脚本和HTML文件完成了,我特意为每个iframe添加了name的属性、自己运行看下效果吧:
那我就开始分析啦:
1、第9行中page.frames()得到所有的frame框架,然后用find(f => f.name() === ‘leftframe’)的函数得到左侧的iframe框架
2、第11行中使用frame1.childFrames()的函数得到儿子iframe框,用的句柄第9行得到的frame1,因为只有一个子框所以我直接用索引的方式得到
3、第13、14行使用儿子iframe框的句柄childframe1进行操作等待和输入文字
4、16-22行脚本是操作第二个框架和第一个iframe是一样的。我就不再多说了,
5、25-26是在最外面的input框输入文字,直接使用page的句柄操作
6、29-40行代码就是第二次输入文本了,为了能看清变化,特地等待了几秒,
7、大家可能会发现,并且疑问我第二次输入是直接进行操作的,没有切换iframe?恩这个值得说明下,我们在用Selenium做UI自动化的时候切换进去一个iframe之后,操作其它的元素必须跳出来才能操作,因为Selenium只有driver一个句柄可以进行操作,但是看看咱们的puppeteer脚本里的句柄:page、frame1、frmae2、childframe1、childframe2,足足五个句柄可以使用呀,当然可以直接进行操作了,手动滑稽。这就是我喜欢这个框架的原因,
8、补充说一下5个句柄代表的含义,如果对句柄这个概念很懵逼的小伙伴自行去百度,我就不过多解释啦!

  • page:代表整个页面的句柄,从标签body开始的,可以进行所有的操作
  • frame1:代表第一个左侧的,iframe框架的句柄,只能操作左侧iframe框架里面的元素
  • frmae2:代表第二个右侧的,iframe框架的句柄,只能操作右侧iframe框架里面的元素
  • childframe1:代表第一个左侧的iframe框架子元素的句柄,只能操作子元素里面的元素
  • childframe2:代表第二个右侧的iframe框架子元素的句柄,只能操作子元素里面的元素

输入我用了另外一个代码childframe2.$eval(),这个是一个很强大的语法用处很大,后续我会把用法写出来,有兴趣的可以先看下官方API:

childframe2.$eval('#input_01',input => input.value='第二次输入:leftframe')

3、当iframe没有name属性、也不想用索引查找时。解决方法如下:使用frame.title()和frame.url()两个函数

思路一:使用page.$eval()函数为iframe框架添加name属性(结果失败)

const puppeteer = require('puppeteer');

(async () => {
 const browser = await puppeteer.launch({headless:false});
 const page = await browser.newPage();
 await page.goto('https://mail.163.com/');
 await page.setViewport({width:1000,height:800});
 //切换iframe框代码
 await page.waitFor('#switchAccountLogin');
 await page.click('#switchAccountLogin');//默认是扫二维码的,点击密码登录,调出输入框
 await page.waitFor('[id*="x-URS-iframe"]');//等待我的iframe出现
 await page.$eval('[id*="x-URS-iframe"]',f => f.setAttribute('name','iframe_01'));//添加name属性
 const frame = await page.frames().find(f => f.name() === 'iframe_01');//使用name进行对比
 await frame.waitFor('.j-inputtext.dlemail');//等待用户名输入框出现
 await frame.type('.j-inputtext.dlemail','12345');//输入账户
 await frame.waitFor('.dlpwd');//等待密码框出现
 await frame.type('.dlpwd','12345');//输入密码
 //等待3秒后退出浏览器
 await page.waitFor(3000);
 await browser.close();
})();

重要的时下面这两句,不过很遗憾,虽然自己新增了name属性,但是page.frames()函数并没有识别到这个属性,最后以失败告终,小伙伴可以试下

await page.$eval('[id*="x-URS-iframe"]',f => f.setAttribute('name','iframe_01'));//添加name属性
const frame = await page.frames().find(f => f.name() === 'iframe_01');//使用name进行对比

最后找到了失败原因:框架只能附加到页面一次,
Node+puppeteer学习笔记(三)--API问题解决--切换frame和iframe框_第4张图片

思路二:使用title()函数得到iframe框架的标题,进行判断(成功)

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless:false});
  const page = await browser.newPage();
  await page.goto('https://mail.163.com/');
  await page.setViewport({width:1000,height:800});
  //切换iframe框代码
  await page.waitFor('#switchAccountLogin');
  await page.click('#switchAccountLogin');//默认是扫二维码的,点击密码登录,调出输入框
  await page.waitFor('[id*="x-URS-iframe"]');//等待我的iframe出现
  //得到ifram里面的title属性,进行对比
  const frames = await page.frames();//得到所有的frame和iframe框架
  for (var i of frames) {	//使用循环取出iframe
  	if (await i.title() === 'URS组件') {var frame = i;} //使用title()函数得到里面的title标题进行对比
  };
  await frame.waitFor('.j-inputtext.dlemail');//等待用户名输入框出现
  await frame.type('.j-inputtext.dlemail','12345');//输入账户
  await frame.waitFor('.dlpwd');//等待密码框出现
  await frame.type('.dlpwd','12345');//输入密码
  //等待3秒后退出浏览器
  await page.waitFor(3000);
  await browser.close();
})();

重要的是下列两句,说明一下:

  const frames = await page.frames();//得到所有的frame和iframe框架
  for (var i of frames) {	//使用循环取出iframe
  	if (await i.title() == 'URS组件') {var frame = i;} //使用title()函数得到里面的title标题进行对比
  };

首先使用page.frames()得到所有的iframe,接下来使用for循环取出每个iframe,并得到title,进行对比。
标题是在iframe中年查看的,如下图
Node+puppeteer学习笔记(三)--API问题解决--切换frame和iframe框_第5张图片

思路三:使用page.$eval()函数得到iframe框架的src属性,使用url()函数得到网址,判断(成功)

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless:false});
  const page = await browser.newPage();
  await page.goto('https://mail.163.com/');
  await page.setViewport({width:1000,height:800});
  //切换iframe框代码
  await page.waitFor('#switchAccountLogin');
  await page.click('#switchAccountLogin');//默认是扫二维码的,点击密码登录,调出输入框
  await page.waitFor('[id*="x-URS-iframe"]');//等待我的iframe出现
  //得到ifram里面的title属性,进行对比
  const url = await page.$eval('[id*="x-URS-iframe"]',f => f.getAttribute('src'));//得到属性值
  const frames = await page.frames();//得到所有的frame和iframe框架
  for (var i of frames) {	//使用循环取出iframe
  	if (await i.url() == url) {var frame = i;} //使用url()函数得到里面的url标题进行对比
  };
  await frame.waitFor('.j-inputtext.dlemail');//等待用户名输入框出现
  await frame.type('.j-inputtext.dlemail','12345');//输入账户
  await frame.waitFor('.dlpwd');//等待密码框出现
  await frame.type('.dlpwd','12345');//输入密码
  //等待3秒后退出浏览器
  await page.waitFor(3000);
  await browser.close();
})();

这个和title差不多,只不过使用的是url函数,并且要使用page.$eval()得到src的值作对比:

  const url = await page.$eval('[id*="x-URS-iframe"]',f => f.getAttribute('src'));//得到属性值
  const frames = await page.frames();//得到所有的frame和iframe框架
  for (var i of frames) {	//使用循环取出iframe
  	if (await i.url() == url) {var frame = i;} //使用url()函数得到里面的url标题进行对比
  };

你可能感兴趣的:(Node+puppeteer学习笔记(三)--API问题解决--切换frame和iframe框)