为了实现UI自动化测试,在web端的应用,而做的一次调研。由于原项目自动化框架是基于C#,所以研究了Puppeteer在Visual Studio 2019上如何安装和使用。
WebView2
Microsoft Edge WebView2 控件允许在本机应用中嵌入 web 技术(HTML、CSS 以及 JavaScript)。 WebView2 控件使用 Microsoft Edge 作为绘制引擎,以在本机应用中显示 web 内容。
使用 WebView2 可以在本机应用的不同部分嵌入 Web 代码,或在单个 WebView2 实例中生成所有本机应用。
Puppeteer与PuppeteerSharp
- Puppeteer 是一个Chrome官方团队提供的node库,它可以通过 Puppeteer 的提供的 API 直接控制 Chrome 或 Chromiun。
- Puppeteer Sharp是官方Node.JS Puppeteer API的.NET移植。本文主要介绍使用Puppeteer Sharp方法代码和文档。可以生成网页截图,将网页保存成pdf文件,执行Javascript(js)代码等。
提示:以下是本篇文章正文内容,下面案例可供参考
一、Puppeteer可以做什么
1) 生成网页截图或者 PDF
2) 爬取SPA应用,并生成预渲染内容(即“SSR” 服务端渲染)
3) 高级爬虫,可以爬取大量异步渲染内容的网页
4) 模拟键盘输入、表单自动提交、登录网页等
5) 创建一个最新的自动化测试环境,实现 UI 自动化测试
6) 捕获站点的时间线,以便追踪网站、帮助分析网站性能问题
7) 用于测试 Chrome 扩展程序
二、Puppeteer 架构图
Puppeteer API 是分层的,并反映了浏览器的结构。
Puppeteer
使用 DevTools Protocol 与浏览器通信Browser
实例可以拥有多个浏览器上下文BrowserContext
实例定义了一个浏览会话,可以拥有多个页面Page
至少有一个 frame :main frame。可能还有其他由 iframe 创建的 frame 或标签Frame
至少有一个可执行上下文 - 默认执行上下文 - 在其中执行 frame 的 JavaScript 。一个 Frame 可能有与 extensions 有关联的其他执行上下文Worker
具有单个执行上下文,便于与 WebWorkers 交互
三、示例
安装PuppeteerSharp
Nuget是一个.NET平台下的开源的项目,它是Visual Studio的扩展。在使用Visual Studio开发基于.NET
Framework的应用时,Nuget能把在项目中添加、移除和更新引用的工作变得更加快捷方便。
NuGet能更方便地把一些dll和文件(如jquery)添加到项目中,而不需要从文件中复制拷贝。说简单点,就是Nuget可以自动管理.NET项目依赖关系,但是项目要打包成Nuget包。vs2019可以直接发布生成Nuget包。Nuget包放在自已的Nuget服务器上,或者官方的Nuget服务器上。
1.打开NuGet包管理,点击第二个>>管理解决方案的NuGet
2.搜索puppeteer,下载
1.获取百度网页生成PDF,代码如下(示例):
using System;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using PuppeteerSharp;//引入库
namespace Class_learn
{
class Program
{
public static async Task Main(string[] args)
{
//启动Chrome/ium浏览器的选项。
var options = new LaunchOptions
{
Headless = true
};
Console.WriteLine("Downloading chromium");
//初始化PuppeteerSharp的一个新实例。BrowserFetcher类。
using var browserFetcher = new BrowserFetcher();
//下载修订版本。解析完成下载的任务。
await browserFetcher.DownloadAsync();
Console.WriteLine("Navigating baidu");
//该方法使用给定的参数启动一个浏览器实例。浏览器将在浏览器被释放时关闭。
using (var browser = await Puppeteer.LaunchAsync(options))
//创建新页面
//任务解析为一个新的PuppeteerSharp。页面对象
using (var page = await browser.NewPageAsync())
{
await page.GoToAsync("http://www.baidu.com");
Console.WriteLine("Generating PDF");
await page.PdfAsync(Path.Combine(Directory.GetCurrentDirectory(), "baidu.pdf"));
Console.WriteLine("Export completed");
if (!args.Any(arg => arg == "auto-exit"))
{
Console.ReadLine();
}
}
}
}
}
结果
2.获取百度网页生成图片,并改变大小,代码如下(示例):
using System;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using PuppeteerSharp;
namespace Class_learn
{
class Program
{
public static async Task Main(string[] args)
{
var options = new LaunchOptions
{
Headless = true
};
Console.WriteLine("Downloading chromium");
using var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
Console.WriteLine("Navigating baidu");
using (var browser = await Puppeteer.LaunchAsync(options))
using (var page = await browser.NewPageAsync())
{
await page.GoToAsync("http://www.baidu.com");
Console.WriteLine("Generating Picture");
//await page.PdfAsync(Path.Combine(Directory.GetCurrentDirectory(), "baidu.pdf"));
//改变图片大小应该放在输出之前
//设置视口。在一个浏览器中有多个页面的情况下,每个页面
//可以有自己的视口大。PuppeteerSharp.Page.SetViewportAsync (PuppeteerSharp.ViewPortOptions)
//将调整页面大小。很多网站都不希望手机改变尺寸,所以应该在导航到页面之前设置视口。
await page.SetViewportAsync(new ViewPortOptions
{
Width = 2000,
Height = 600
});
await page.ScreenshotAsync(Path.Combine(Directory.GetCurrentDirectory(), "baidu.png"));
Console.WriteLine("Export completed");
if (!args.Any(arg => arg == "auto-exit"))
{
Console.ReadLine();
}
}
}
}
}
ViewPortOptions设置参数
默认结果
改变后
3.向网页中注入HTML
using System;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using PuppeteerSharp;
namespace Class_learn
{
class Program
{
public static async Task Main(string[] args)
{
var options = new LaunchOptions
{
Headless = true
};
Console.WriteLine("Downloading chromium");
using var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
Console.WriteLine("Navigating baidu");
using (var browser = await Puppeteer.LaunchAsync(options))
using (var page = await browser.NewPageAsync())
{
await page.SetContentAsync("<div>My Receipt</div>");
//获取页面的完整HTML内容,包括文档类型。
var result = await page.GetContentAsync();
await page.PdfAsync(Path.Combine(Directory.GetCurrentDirectory(), "test.pdf"));
Console.WriteLine(result);
Console.WriteLine("Export completed");
if (!args.Any(arg => arg == "auto-exit"))
{
Console.ReadLine();
}
}
}
}
}
结果
4.从一个页面获得所有的Href链接
这个示例演示了如何从页面获取链接。
using System;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using PuppeteerSharp;
using System.Diagnostics;
namespace Class_learn
{
class Program
{
static async Task Main(string[] args)
{
var options = new LaunchOptions { Headless = true };
Console.WriteLine("Downloading chromium");
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
Console.WriteLine("Navigating to baidu.com");
using (var browser = await Puppeteer.LaunchAsync(options))
using (var page = await browser.NewPageAsync())
{
await page.GoToAsync("http://www.baidu.com");
var jsSelectAllAnchors = @"Array.from(document.querySelectorAll('a')).map(a => a.href);";
var urls = await page.EvaluateExpressionAsync<string[]>(jsSelectAllAnchors);
foreach (string url in urls)
{
Console.WriteLine($"Url: {url}");
}
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
}
}
}
结果
Downloading chromium
Navigating to baidu.com
Url: https://www.baidu.com/
Url: javascript:;
Url: https://passport.baidu.com/v2/?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F&sms=5
Url: http://news.baidu.com/
Url: https://www.hao123.com/?src=from_pc
Url: http://map.baidu.com/
Url: http://tieba.baidu.com/
Url: https://haokan.baidu.com/?sfrom=baidu-top
Url: http://image.baidu.com/
Url: https://pan.baidu.com/?from=1026962h
Url: http://www.baidu.com/more/
Url: http://fanyi.baidu.com/
Url: http://xueshu.baidu.com/
Url: https://wenku.baidu.com/
Url: https://baike.baidu.com/
Url: https://zhidao.baidu.com/
Url: https://jiankang.baidu.com/widescreen/home
Url: http://e.baidu.com/ebaidu/home?refer=887
Url: https://live.baidu.com/
Url: http://music.taihe.com/
Url: http://www.baidu.com/more/
Url: https://passport.baidu.com/v2/?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F&sms=5
Url: javascript:;
Url: javascript:;
Url: https://www.baidu.com/
Url: javascript:;
Url: javascript:;
Url: javascript:;
Url: https://top.baidu.com/board?platform=pc&sa=pcindex_entry
Url:
Url: https://www.baidu.com/s?wd=40%E7%A7%92%E6%B2%89%E6%B5%B8%E5%BC%8F%E7%9C%8B%E9%97%AE%E5%A4%A9%E5%87%BA%E5%BE%81&sa=fyb_n_homepage&rsv_dl=fyb_n_homepage&from=super&cl=3&tn=baidutop10&fr=top1000&rsv_idx=2&hisfilter=1
Url: https://www.baidu.com/s?wd=%E5%AE%9E%E4%BD%93%E7%BB%8F%E6%B5%8E%E6%81%A2%E5%A4%8D%E5%90%91%E5%A5%BD%E9%9F%A7%E6%80%A7%E5%BC%BA&sa=fyb_n_homepage&rsv_dl=fyb_n_homepage&from=super&cl=3&tn=baidutop10&fr=top1000&rsv_idx=2&hisfilter=1
Url: https://www.baidu.com/s?wd=%E4%B8%A4%E5%B2%81%E5%A5%B3%E7%AB%A5%E6%82%A3%E5%B0%96%E9%94%90%E6%B9%BF%E7%96%A3%EF%BC%9F%E8%AD%A6%E6%96%B9%E5%B7%B2%E4%BB%8B%E5%85%A5&sa=fyb_n_homepage&rsv_dl=fyb_n_homepage&from=super&cl=3&tn=baidutop10&fr=top1000&rsv_idx=2&hisfilter=1
Url: https://www.baidu.com/s?wd=%E4%B8%AD%E6%96%B9%E5%9B%9E%E5%BA%94%E4%BD%A9%E6%B4%9B%E8%A5%BF%E6%8B%9F%E8%AE%BF%E5%8F%B0%EF%BC%9A%E4%B8%A5%E9%98%B5%E4%BB%A5%E5%BE%85&sa=fyb_n_homepage&rsv_dl=fyb_n_homepage&from=super&cl=3&tn=baidutop10&fr=top1000&rsv_idx=2&hisfilter=1
Url: https://www.baidu.com/s?wd=%E6%9E%97%E5%BF%97%E9%A2%96%E7%9B%AE%E5%89%8D%E6%84%8F%E8%AF%86%E6%B8%85%E6%A5%9A+%E8%83%BD%E7%AE%80%E5%8D%95%E5%AF%B9%E8%AF%9D&sa=fyb_n_homepage&rsv_dl=fyb_n_homepage&from=super&cl=3&tn=baidutop10&fr=top1000&rsv_idx=2&hisfilter=1
Url: https://www.baidu.com/s?wd=%E7%94%B7%E5%AD%A9%E5%9B%A0%E4%B8%8B%E6%A3%8B%E7%8A%AF%E8%A7%84%E8%A2%AB%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%8A%98%E6%96%AD%E6%89%8B%E6%8C%87&sa=fyb_n_homepage&rsv_dl=fyb_n_homepage&from=super&cl=3&tn=baidutop10&fr=top1000&rsv_idx=2&hisfilter=1
Url: https://home.baidu.com/
Url: http://ir.baidu.com/
Url: http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11000002000001
Url: https://beian.miit.gov.cn/
Url: https://www.baidu.com/licence/
Url: https://www.baidu.com/duty
Url: https://help.baidu.com/
Url: https://www.baidu.com/s?rtt=1&bsst=1&cl=2&tn=news
Url: http://v.baidu.com/v?ct=301989888&rn=20&pn=0&db=0&s=25&ie=utf-8
Url: http://image.baidu.com/i?tn=baiduimage&ps=1&ct=201326592&lm=-1&cl=2&nc=1&ie=utf-8
Url: http://zhidao.baidu.com/q?ct=17&pn=0&tn=ikaslist&rn=10&fr=wwwt
Url: http://wenku.baidu.com/search?lm=0&od=0&ie=utf-8
Url: http://tieba.baidu.com/f?fr=wwwt
Url: https://map.baidu.com/?newmap=1&ie=utf-8&s=s
Url: https://b2b.baidu.com/s?fr=wwwt
Url: http://www.baidu.com/more/
Press any key to continue...
5.从网站上抓取搜索结果。本例搜索developers.google.com/web
获取标记为“Headless Chrome”的文章,并从结果页面中获取结果。
// 声明应用用到的命名空间
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Dynamic;
using System.Linq.Expressions;
using System.Net;
using System.IO;
using System.Diagnostics;
using PuppeteerSharp;
namespace Lenarn_Csharp
{
class Program
{
/// <summary>
/// 从网站上抓取搜索结果
/// 本例搜索developers.google.com/web
/// 获取标记为“Headless Chrome”的文章,并从结果页面中获取结果。
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public static async Task Main(string[] args)
{
var options = new LaunchOptions { Headless = true };
Console.WriteLine("Downloading chromium");
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
Console.WriteLine("Navigating to developers.google.com");
using (var browser = await Puppeteer.LaunchAsync(options))
using (var page = await browser.NewPageAsync())
{
await page.GoToAsync("https://developers.google.com/web/");
// Type into search box.在搜索框中键入
await page.TypeAsync("input.devsite-search-field.devsite-search-query", "Headless Chrome");
// Wait for suggest overlay to appear and click "show all results".
// 等待“建议覆盖”出现,然后单击“显示所有结果”。
var allResultsSelector = ".devsite-suggest-all-results";
await page.WaitForSelectorAsync(allResultsSelector);
await page.ClickAsync(allResultsSelector);
// Wait for the results page to load and display the results.
// 等待结果页面加载并显示结果。
var resultsSelector = "a.gs-title";
await page.WaitForSelectorAsync(resultsSelector);
var links = await page.EvaluateFunctionAsync(@"(resultsSelector) => {
const anchors = Array.from(document.querySelectorAll(resultsSelector));
return anchors.map(anchor => {
const title = anchor.textContent.split('|')[0].trim();
return `${title} - ${anchor.href}`;
});
}", resultsSelector);
foreach (var link in links)
{
Console.WriteLine(link);
}
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
}
}
}
结果
6.在无头浏览器中成功加载网页后,让我们通过搜索本地旅游景点与网页进行交互,并获取景点信息。
// 声明应用用到的命名空间
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Dynamic;
using System.Linq.Expressions;
using System.Net;
using System.IO;
using System.Diagnostics;
using PuppeteerSharp;
namespace Lenarn_Csharp
{
class Program
{
/// <summary>
/// 在无头浏览器中成功加载网页后,通过搜索本地旅游景点与网页进行交互
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public static async Task Main(string[] args)
{
var options = new LaunchOptions { Headless = true };
Console.WriteLine("Downloading chromium");
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
Console.WriteLine("Navigating to developers.google.com");
using (var browser = await Puppeteer.LaunchAsync(options))
using (var page = await browser.NewPageAsync())
{
// Create a new page and go to Bing Maps
//Page page = await browser.NewPageAsync();
await page.GoToAsync("https://www.bing.com/maps");
//拍摄页面截图
await page.ScreenshotAsync("D:\\test\\screenshot.png");
// Search for a local tourist attraction on Bing Maps
await page.WaitForSelectorAsync("input#maps_sb");
await page.ScreenshotAsync("D:\\test\\screenshot1.png");
//使用选择器获取元素并将其聚焦
await page.FocusAsync("input#maps_sb");
await page.ScreenshotAsync("D:\\test\\screenshot2.png");
//Sends a keydown, keypress/input, and keyup event for each character in the text.
await page.Keyboard.TypeAsync("Window of the World, shenzhen, guangdong, china");
await page.ScreenshotAsync("D:\\test\\screenshot3.png");
//使用选择器获取元素,如果需要,将其滚动到视图中,然后使用Puppetersharp.Page。用鼠标单击元素的中心。
await page.ClickAsync(".searchIcon");
await page.ScreenshotAsync("D:\\test\\screenshot4.png");
DateTime dt1 = DateTime.Now;
while ((DateTime.Now - dt1).TotalMilliseconds < 3000)
{
continue;
};
//await page.WaitForNavigationAsync();
await page.ScreenshotAsync("D:\\test\\screenshot5.png");
var resultsSelector = "span.b_address";
await page.WaitForSelectorAsync(resultsSelector);
var links = await page.EvaluateFunctionAsync(@"(resultsSelector) => {
const anchors = Array.from(document.querySelectorAll(resultsSelector));
return anchors.map(anchor => {
const title = anchor.textContent.split('|')[0].trim();
return `${title} - ${anchor.href}`;
});
}", resultsSelector);
foreach (var link in links)
{
Console.WriteLine(link);
}
Console.WriteLine("Press any key to continue...");
Console.ReadLine();
}
}
}
}
结果