Skip to content

Commit 22687ed

Browse files
authored
Screenshots support (#64)
Screenshot feature implemented
1 parent 2dc3661 commit 22687ed

33 files changed

+684
-56
lines changed

lib/PuppeteerSharp.Tests/Page/PdfTests.cs

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,21 @@
77

88
namespace PuppeteerSharp.Tests.Page
99
{
10-
public class PdfTests : IDisposable
10+
public class PdfTests : PuppeteerBaseTest
1111
{
12-
private string _baseDirectory;
13-
Browser _browser;
14-
15-
public PdfTests()
16-
{
17-
_baseDirectory = Path.Combine(Directory.GetCurrentDirectory(), "test-pdf");
18-
var dirInfo = new DirectoryInfo(_baseDirectory);
19-
20-
if (dirInfo.Exists)
21-
{
22-
dirInfo.Delete(true);
23-
}
24-
25-
dirInfo.Create();
26-
_browser = PuppeteerSharp.Puppeteer.LaunchAsync(TestConstants.DefaultBrowserOptions,
27-
TestConstants.ChromiumRevision).GetAwaiter().GetResult();
28-
}
29-
30-
public void Dispose()
31-
{
32-
_browser.CloseAsync().GetAwaiter().GetResult();
33-
}
3412

3513
[Fact]
3614
public async Task ShouldBeAbleToSaveFile()
3715
{
38-
var outputFile = Path.Combine(_baseDirectory, "output.pdf");
16+
var outputFile = Path.Combine(BaseDirectory, "output.pdf");
3917
var fileInfo = new FileInfo(outputFile);
4018

4119
if (fileInfo.Exists)
4220
{
4321
fileInfo.Delete();
4422
}
4523

46-
var page = await _browser.NewPageAsync();
24+
var page = await Browser.NewPageAsync();
4725
await page.PdfAsync(new PdfOptions
4826
{
4927
Path = outputFile
@@ -61,7 +39,7 @@ await page.PdfAsync(new PdfOptions
6139
[Fact]
6240
public async Task ShouldDefaultToPrintInLetterFormat()
6341
{
64-
var page = await _browser.NewPageAsync();
42+
var page = await Browser.NewPageAsync();
6543

6644
var document = PdfReader.Open(await page.PdfAsync(), PdfDocumentOpenMode.ReadOnly);
6745

@@ -73,7 +51,7 @@ public async Task ShouldDefaultToPrintInLetterFormat()
7351
[Fact]
7452
public async Task ShouldSupportSettingCustomFormat()
7553
{
76-
var page = await _browser.NewPageAsync();
54+
var page = await Browser.NewPageAsync();
7755

7856
var document = PdfReader.Open(await page.PdfAsync(new PdfOptions
7957
{
@@ -88,7 +66,7 @@ public async Task ShouldSupportSettingCustomFormat()
8866
[Fact]
8967
public async Task ShouldSupportSettingPaperWidthAndHeight()
9068
{
91-
var page = await _browser.NewPageAsync();
69+
var page = await Browser.NewPageAsync();
9270

9371
var document = PdfReader.Open(await page.PdfAsync(new PdfOptions
9472
{
@@ -104,7 +82,7 @@ public async Task ShouldSupportSettingPaperWidthAndHeight()
10482
[Fact]
10583
public async Task ShouldPrintMultiplePages()
10684
{
107-
var page = await _browser.NewPageAsync();
85+
var page = await Browser.NewPageAsync();
10886
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
10987
// Define width and height in CSS pixels.
11088
var width = 50 * 5 + 1;
@@ -123,7 +101,7 @@ public async Task ShouldPrintMultiplePages()
123101
[Fact]
124102
public async Task ShouldSupportPageRanges()
125103
{
126-
var page = await _browser.NewPageAsync();
104+
var page = await Browser.NewPageAsync();
127105
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
128106
// Define width and height in CSS pixels.
129107
var width = 50 * 5 + 1;
@@ -141,7 +119,7 @@ public async Task ShouldSupportPageRanges()
141119
[Fact]
142120
public async Task ShowThrowFormatIsUnknown()
143121
{
144-
var page = await _browser.NewPageAsync();
122+
var page = await Browser.NewPageAsync();
145123
var exception = await Assert.ThrowsAsync<ArgumentException>(async () =>
146124
{
147125
await page.PdfAsync(new PdfOptions
@@ -156,7 +134,7 @@ await page.PdfAsync(new PdfOptions
156134
[Fact]
157135
public async Task ShouldThrowIfUnitsAreUnknown()
158136
{
159-
var page = await _browser.NewPageAsync();
137+
var page = await Browser.NewPageAsync();
160138
var exception = await Assert.ThrowsAsync<ArgumentException>(async () =>
161139
{
162140
await page.PdfAsync(new PdfOptions
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Threading.Tasks;
5+
using SixLabors.ImageSharp;
6+
using SixLabors.ImageSharp.PixelFormats;
7+
using Xunit;
8+
9+
namespace PuppeteerSharp.Tests.Page
10+
{
11+
public class ScreenshotTests : PuppeteerBaseTest
12+
{
13+
14+
[Fact]
15+
public async Task ShouldWorkWithFile()
16+
{
17+
var outputFile = Path.Combine(BaseDirectory, "output.png");
18+
var fileInfo = new FileInfo(outputFile);
19+
var page = await Browser.NewPageAsync();
20+
21+
await page.SetViewport(new ViewPortOptions
22+
{
23+
Width = 500,
24+
Height = 500
25+
});
26+
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
27+
28+
if (fileInfo.Exists)
29+
{
30+
fileInfo.Delete();
31+
}
32+
33+
await page.ScreenshotAsync(outputFile);
34+
35+
fileInfo = new FileInfo(outputFile);
36+
Assert.True(new FileInfo(outputFile).Length > 0);
37+
Assert.True(PixelMatch("screenshot-sanity.png", outputFile));
38+
39+
if (fileInfo.Exists)
40+
{
41+
fileInfo.Delete();
42+
}
43+
}
44+
45+
[Fact]
46+
public async Task ShouldWork()
47+
{
48+
var page = await Browser.NewPageAsync();
49+
await page.SetViewport(new ViewPortOptions
50+
{
51+
Width = 500,
52+
Height = 500
53+
});
54+
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
55+
var screenshot = await page.ScreenshotStreamAsync();
56+
Assert.True(PixelMatch("screenshot-sanity.png", screenshot));
57+
}
58+
59+
[Fact]
60+
public async Task ShouldClipRect()
61+
{
62+
var page = await Browser.NewPageAsync();
63+
await page.SetViewport(new ViewPortOptions
64+
{
65+
Width = 500,
66+
Height = 500
67+
});
68+
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
69+
var screenshot = await page.ScreenshotStreamAsync(new ScreenshotOptions
70+
{
71+
Clip = new Clip
72+
{
73+
X = 50,
74+
Y = 100,
75+
Width = 150,
76+
Height = 100
77+
}
78+
});
79+
Assert.True(PixelMatch("screenshot-clip-rect.png", screenshot));
80+
}
81+
82+
[Fact]
83+
public async Task ShouldWorkForOffscreenClip()
84+
{
85+
var page = await Browser.NewPageAsync();
86+
await page.SetViewport(new ViewPortOptions
87+
{
88+
Width = 500,
89+
Height = 500
90+
});
91+
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
92+
var screenshot = await page.ScreenshotStreamAsync(new ScreenshotOptions
93+
{
94+
Clip = new Clip
95+
{
96+
X = 50,
97+
Y = 600,
98+
Width = 100,
99+
Height = 100
100+
}
101+
});
102+
Assert.True(PixelMatch("screenshot-offscreen-clip.png", screenshot));
103+
}
104+
105+
[Fact]
106+
public async Task ShouldRunInParallel()
107+
{
108+
var page = await Browser.NewPageAsync();
109+
await page.SetViewport(new ViewPortOptions
110+
{
111+
Width = 500,
112+
Height = 500
113+
});
114+
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
115+
116+
var tasks = new List<Task<Stream>>();
117+
for (var i = 0; i < 3; ++i)
118+
{
119+
tasks.Add(page.ScreenshotStreamAsync(new ScreenshotOptions
120+
{
121+
Clip = new Clip
122+
{
123+
X = 50 * i,
124+
Y = 0,
125+
Width = 50,
126+
Height = 50
127+
}
128+
}));
129+
}
130+
131+
await Task.WhenAll(tasks);
132+
Assert.True(PixelMatch("grid-cell-1.png", tasks[0].Result));
133+
}
134+
135+
[Fact]
136+
public async Task ShouldTakeFullPageScreenshots()
137+
{
138+
var page = await Browser.NewPageAsync();
139+
await page.SetViewport(new ViewPortOptions
140+
{
141+
Width = 500,
142+
Height = 500
143+
});
144+
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
145+
var screenshot = await page.ScreenshotStreamAsync(new ScreenshotOptions
146+
{
147+
FullPage = true
148+
});
149+
Assert.True(PixelMatch("screenshot-grid-fullpage.png", screenshot));
150+
}
151+
152+
[Fact]
153+
public async Task ShouldRunInParallelInMultiplePages()
154+
{
155+
var n = 2;
156+
var pageTasks = new List<Task<PuppeteerSharp.Page>>();
157+
for (var i = 0; i < n; i++)
158+
{
159+
Func<Task<PuppeteerSharp.Page>> func = async () =>
160+
{
161+
var page = await Browser.NewPageAsync();
162+
await page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
163+
return page;
164+
};
165+
166+
pageTasks.Add(func());
167+
}
168+
169+
await Task.WhenAll(pageTasks);
170+
171+
var screenshotTasks = new List<Task<Stream>>();
172+
for (var i = 0; i < n; i++)
173+
{
174+
screenshotTasks.Add(pageTasks[i].Result.ScreenshotStreamAsync(new ScreenshotOptions
175+
{
176+
Clip = new Clip
177+
{
178+
X = 50 * i,
179+
Y = 0,
180+
Width = 50,
181+
Height = 50
182+
}
183+
}));
184+
}
185+
186+
await Task.WhenAll(screenshotTasks);
187+
188+
for (var i = 0; i < n; i++)
189+
{
190+
Assert.True(PixelMatch($"grid-cell-{i}.png", screenshotTasks[i].Result));
191+
192+
}
193+
194+
var closeTasks = new List<Task>();
195+
for (var i = 0; i < n; i++)
196+
{
197+
closeTasks.Add(pageTasks[i].Result.CloseAsync());
198+
}
199+
200+
await Task.WhenAll(closeTasks);
201+
}
202+
203+
[Fact]
204+
public async Task ShouldAllowTransparency()
205+
{
206+
var page = await Browser.NewPageAsync();
207+
await page.SetViewport(new ViewPortOptions
208+
{
209+
Width = 100,
210+
Height = 100
211+
});
212+
await page.GoToAsync(TestConstants.EmptyPage);
213+
var screenshot = await page.ScreenshotStreamAsync(new ScreenshotOptions
214+
{
215+
OmitBackground = true
216+
});
217+
218+
Assert.True(PixelMatch("transparent.png", screenshot));
219+
}
220+
221+
[Fact]
222+
public async Task ShouldWorkWithOddClipSizeOnRetinaDisplays()
223+
{
224+
var page = await Browser.NewPageAsync();
225+
var screenshot = await page.ScreenshotStreamAsync(new ScreenshotOptions
226+
{
227+
Clip = new Clip
228+
{
229+
X = 0,
230+
Y = 0,
231+
Width = 11,
232+
Height = 11
233+
}
234+
});
235+
236+
Assert.True(PixelMatch("screenshot-clip-odd-size.png", screenshot));
237+
}
238+
239+
private bool PixelMatch(string screenShotFile, string fileName)
240+
{
241+
using (Stream stream = File.Open(fileName, FileMode.Open))
242+
{
243+
return PixelMatch(screenShotFile, stream);
244+
}
245+
}
246+
247+
private bool PixelMatch(string screenShotFile, Stream screenshot)
248+
{
249+
const int pixelThreshold = 10;
250+
const decimal totalTolerance = 0.05m;
251+
252+
var baseImage = Image.Load(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "Screenshots", screenShotFile));
253+
var compareImage = Image.Load(screenshot);
254+
255+
//Just for debugging purpose
256+
compareImage.Save(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "Screenshots", "test.png"));
257+
258+
if (baseImage.Width != compareImage.Width || baseImage.Height != compareImage.Height)
259+
{
260+
return false;
261+
}
262+
263+
var rgb1 = default(Rgb24);
264+
var rgb2 = default(Rgb24);
265+
var invalidPixelsCount = 0;
266+
267+
for (int y = 0; y < baseImage.Height; y++)
268+
{
269+
for (int x = 0; x < baseImage.Width; x++)
270+
{
271+
var pixelA = baseImage[x, y];
272+
var pixelB = compareImage[x, y];
273+
274+
pixelA.ToRgb24(ref rgb1);
275+
pixelB.ToRgb24(ref rgb2);
276+
277+
if (Math.Abs(rgb1.R - rgb2.R) > pixelThreshold ||
278+
Math.Abs(rgb1.G - rgb2.G) > pixelThreshold ||
279+
Math.Abs(rgb1.B - rgb2.B) > pixelThreshold)
280+
{
281+
invalidPixelsCount++;
282+
}
283+
}
284+
}
285+
286+
return (invalidPixelsCount / (baseImage.Height * baseImage.Width)) < totalTolerance;
287+
}
288+
}
289+
}

0 commit comments

Comments
 (0)