Skip to content

Commit 3c00dfd

Browse files
authored
Implement Tracing (#170)
1 parent a65943e commit 3c00dfd

File tree

6 files changed

+272
-3
lines changed

6 files changed

+272
-3
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
9+
using Xunit;
10+
11+
namespace PuppeteerSharp.Tests.Tracing
12+
{
13+
[Collection("PuppeteerLoaderFixture collection")]
14+
public class TracingTests : PuppeteerPageBaseTest
15+
{
16+
private string _file;
17+
18+
public TracingTests()
19+
{
20+
_file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
21+
}
22+
23+
protected override async Task DisposeAsync()
24+
{
25+
await base.DisposeAsync();
26+
27+
int attempts = 0;
28+
const int maxAttempts = 5;
29+
30+
while (true)
31+
{
32+
try
33+
{
34+
attempts++;
35+
if (File.Exists(_file))
36+
{
37+
File.Delete(_file);
38+
}
39+
break;
40+
}
41+
catch (UnauthorizedAccessException)
42+
{
43+
if (attempts == maxAttempts)
44+
{
45+
break;
46+
}
47+
48+
await Task.Delay(1000);
49+
}
50+
}
51+
}
52+
53+
[Fact]
54+
public async Task ShouldOutputATrace()
55+
{
56+
await Page.Tracing.StartAsync(new TracingOptions
57+
{
58+
Screenshots = true,
59+
Path = _file
60+
});
61+
await Page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
62+
await Page.Tracing.StopAsync();
63+
64+
Assert.True(File.Exists(_file));
65+
}
66+
67+
[Fact]
68+
public async Task ShouldRunWithCustomCategoriesProvided()
69+
{
70+
await Page.Tracing.StartAsync(new TracingOptions
71+
{
72+
Screenshots = true,
73+
Path = _file,
74+
Categories = new List<string>
75+
{
76+
"disabled-by-default-v8.cpu_profiler.hires"
77+
}
78+
});
79+
80+
await Page.Tracing.StopAsync();
81+
82+
using (var file = File.OpenText(_file))
83+
using (var reader = new JsonTextReader(file))
84+
{
85+
var traceJson = JToken.ReadFrom(reader);
86+
Assert.Contains("disabled-by-default-v8.cpu_profiler.hires", traceJson["metadata"]["trace-config"].ToString());
87+
}
88+
}
89+
90+
[Fact]
91+
public async Task ShouldThrowIfTracingOnTwoPages()
92+
{
93+
await Page.Tracing.StartAsync(new TracingOptions
94+
{
95+
Path = _file,
96+
});
97+
var newPage = await Browser.NewPageAsync();
98+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
99+
{
100+
await Page.Tracing.StartAsync(new TracingOptions
101+
{
102+
Path = _file,
103+
});
104+
});
105+
106+
await newPage.CloseAsync();
107+
await Page.Tracing.StopAsync();
108+
}
109+
}
110+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace PuppeteerSharp.Messaging
5+
{
6+
public class IOReadResponse
7+
{
8+
[JsonProperty("eof")]
9+
public bool Eof { get; set; }
10+
[JsonProperty("data")]
11+
public string Data { get; set; }
12+
}
13+
}

lib/PuppeteerSharp/Session.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Text;
77
using System.Threading;
88
using Newtonsoft.Json.Linq;
9+
using System.IO;
910

1011
namespace PuppeteerSharp
1112
{
@@ -30,6 +31,8 @@ public Session(Connection connection, string targetId, string sessionId)
3031
public string SessionId { get; private set; }
3132
public Connection Connection { get; private set; }
3233
public event EventHandler<MessageEventArgs> MessageReceived;
34+
public event EventHandler<TracingCompleteEventArgs> TracingComplete;
35+
3336
#endregion
3437

3538
#region Public Methods
@@ -133,6 +136,13 @@ internal void OnMessage(string message)
133136
}
134137
}
135138
}
139+
else if (obj.method == "Tracing.tracingComplete")
140+
{
141+
TracingComplete?.Invoke(this, new TracingCompleteEventArgs
142+
{
143+
Stream = objAsJObject["params"].Value<string>("stream")
144+
});
145+
}
136146
else
137147
{
138148
MessageReceived?.Invoke(this, new MessageEventArgs

lib/PuppeteerSharp/Tracing.cs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,117 @@
1-
namespace PuppeteerSharp
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Threading.Tasks;
5+
using PuppeteerSharp.Messaging;
6+
7+
namespace PuppeteerSharp
28
{
39
public class Tracing
410
{
5-
private Session client;
11+
private Session _client;
12+
private bool _recording;
13+
private string _path;
14+
private static readonly List<string> _defaultCategories = new List<string>()
15+
{
16+
"-*",
17+
"devtools.timeline",
18+
"v8.execute",
19+
"disabled-by-default-devtools.timeline",
20+
"disabled-by-default-devtools.timeline.frame",
21+
"toplevel",
22+
"blink.console",
23+
"blink.user_timing",
24+
"latencyInfo",
25+
"disabled-by-default-devtools.timeline.stack",
26+
"disabled-by-default-v8.cpu_profiler"
27+
};
628

729
public Tracing(Session client)
830
{
9-
this.client = client;
31+
_client = client;
32+
}
33+
34+
/// <summary>
35+
/// Starts tracing.
36+
/// </summary>
37+
/// <returns>Start task</returns>
38+
/// <param name="options">Tracing options</param>
39+
public async Task StartAsync(TracingOptions options)
40+
{
41+
if (_recording)
42+
{
43+
throw new InvalidOperationException("Cannot start recording trace while already recording trace.");
44+
}
45+
46+
if (string.IsNullOrEmpty(options.Path))
47+
{
48+
throw new ArgumentException("Must specify a path to write trace file to.");
49+
}
50+
51+
52+
var categories = options.Categories ?? _defaultCategories;
53+
54+
if (options.Screenshots)
55+
{
56+
categories.Add("disabled-by-default-devtools.screenshot");
57+
}
58+
59+
_path = options.Path;
60+
_recording = true;
61+
62+
await _client.SendAsync("Tracing.start", new
63+
{
64+
transferMode = "ReturnAsStream",
65+
categories = string.Join(", ", categories)
66+
});
67+
}
68+
69+
/// <summary>
70+
/// Stops tracing
71+
/// </summary>
72+
/// <returns>Stop task</returns>
73+
public async Task StopAsync()
74+
{
75+
var taskWrapper = new TaskCompletionSource<bool>();
76+
77+
async void EventHandler(object sender, TracingCompleteEventArgs e)
78+
{
79+
await ReadStream(e.Stream, _path);
80+
_client.TracingComplete -= EventHandler;
81+
taskWrapper.SetResult(true);
82+
};
83+
84+
_client.TracingComplete += EventHandler;
85+
86+
await _client.SendAsync("Tracing.end");
87+
88+
_recording = false;
89+
90+
await taskWrapper.Task;
91+
}
92+
93+
private async Task ReadStream(string stream, string path)
94+
{
95+
using (var fs = new StreamWriter(path))
96+
{
97+
bool eof = false;
98+
99+
while (!eof)
100+
{
101+
var response = await _client.SendAsync<IOReadResponse>("IO.read", new
102+
{
103+
handle = stream
104+
});
105+
106+
eof = response.Eof;
107+
108+
await fs.WriteAsync(response.Data);
109+
}
110+
}
111+
await _client.SendAsync("IO.close", new
112+
{
113+
handle = stream
114+
});
10115
}
11116
}
12117
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
using System.IO;
3+
namespace PuppeteerSharp
4+
{
5+
public class TracingCompleteEventArgs : EventArgs
6+
{
7+
public string Stream { get; internal set; }
8+
}
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Collections.Generic;
2+
namespace PuppeteerSharp
3+
{
4+
public class TracingOptions
5+
{
6+
/// <summary>
7+
/// Gets or sets a value indicating whether Tracing should captures screenshots in the trace
8+
/// </summary>
9+
/// <value>Screenshots option</value>
10+
public bool Screenshots { get; set; }
11+
/// <summary>
12+
/// A path to write the trace file to
13+
/// </summary>
14+
/// <value>The path.</value>
15+
public string Path { get; set; }
16+
/// <summary>
17+
/// Specify custom categories to use instead of default.
18+
/// </summary>
19+
/// <value>The categories.</value>
20+
public List<string> Categories { get; set; }
21+
}
22+
}

0 commit comments

Comments
 (0)