How to use KillAsync method of PuppeteerSharp.States.DisposedState class

Best Puppeteer-sharp code snippet using PuppeteerSharp.States.DisposedState.KillAsync

Run Puppeteer-sharp automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

DisposedState.cs

Source: DisposedState.cs Github

copy
1using System;
2using System.Threading.Tasks;
3
4namespace PuppeteerSharp.States
5{
6    internal class DisposedState : State
7    {
8        public DisposedState(StateManager stateManager) : base(stateManager)
9        {
10        }
11
12        public override Task EnterFromAsync(LauncherBase p, State fromState, TimeSpan timeSpan)
13        {
14            if (fromState == StateManager.Exited)
15            {
16                return null;
17            }
18
19            Kill(p);
20
21            p.ExitCompletionSource.TrySetException(new ObjectDisposedException(p.ToString()));
22            p.TempUserDataDir?.Dispose();
23
24            return null;
25        }
26
27        public override Task StartAsync(LauncherBase p) => throw new ObjectDisposedException(p.ToString());
28
29        public override Task ExitAsync(LauncherBase p, TimeSpan timeout) => throw new ObjectDisposedException(p.ToString());
30
31        public override Task KillAsync(LauncherBase p) => throw new ObjectDisposedException(p.ToString());
32
33        public override void Dispose(LauncherBase p)
34        {
35            // Nothing to do
36        }
37    }
38}
39
Full Screen

LauncherBase.cs

Source: LauncherBase.cs Github

copy
1using PuppeteerSharp.Helpers;
2using System;
3using System.Collections;
4using System.Collections.Generic;
5using System.Diagnostics;
6using System.Text;
7using System.Text.RegularExpressions;
8using System.Threading;
9using System.Threading.Tasks;
10
11namespace PuppeteerSharp
12{
13    /// <summary>
14    /// Represents a Base process and any associated temporary user data directory that have created
15    /// by Puppeteer and therefore must be cleaned up when no longer needed.
16    /// </summary>
17    public class LauncherBase : IDisposable
18    {
19
20        #region Instance fields
21
22        private readonly LaunchOptions _options;
23        private readonly TaskCompletionSource<string> _startCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
24        private readonly TaskCompletionSource<bool> _exitCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
25        private State _currentState = State.Initial;
26
27        #endregion
28
29        #region Constructor
30
31        /// <summary>
32        /// Creates a new <see cref="LauncherBase"/> instance.
33        /// </summary>
34        /// <param name="executable">Full path of executable.</param>
35        /// <param name="options">Options for launching Base.</param>
36        /// <param name="loggerFactory">Logger factory</param>
37        public LauncherBase(string executable, LaunchOptions options)
38        {
39            _options = options;
40
41            Process = new Process
42            {
43                EnableRaisingEvents = true
44            };
45            Process.StartInfo.UseShellExecute = false;
46            Process.StartInfo.FileName = executable;
47            Process.StartInfo.RedirectStandardError = true;
48
49            SetEnvVariables(Process.StartInfo.Environment, options.Env, Environment.GetEnvironmentVariables());
50
51            if (options.DumpIO)
52            {
53                Process.ErrorDataReceived += (sender, e) => Console.Error.WriteLine(e.Data);
54            }
55        }
56
57        #endregion
58
59        #region Dispose
60
61        /// <summary>
62        /// Finalizer.
63        /// </summary>
64        ~LauncherBase()
65        {
66            Dispose(false);
67        }
68
69        /// <inheritdoc />
70        public void Dispose()
71        {
72            Dispose(true);
73            GC.SuppressFinalize(this);
74        }
75
76        /// <summary>
77        /// Disposes Base process and any temporary user directory.
78        /// </summary>
79        /// <param name="disposing">Indicates whether disposal was initiated by <see cref="Dispose()"/> operation.</param>
80        protected virtual void Dispose(bool disposing) => _currentState.Dispose(this);
81
82        #endregion
83
84        #region Properties
85
86        /// <summary>
87        /// Gets Base process details.
88        /// </summary>
89        public Process Process { get; }
90
91        /// <summary>
92        /// Gets Base endpoint.
93        /// </summary>
94        public string EndPoint => _startCompletionSource.Task.IsCompleted
95            ? _startCompletionSource.Task.Result
96            : null;
97
98        /// <summary>
99        /// Indicates whether Base process is exiting.
100        /// </summary>
101        public bool IsExiting => _currentState.IsExiting;
102
103        /// <summary>
104        /// Indicates whether Base process has exited.
105        /// </summary>
106        public bool HasExited => _currentState.IsExited;
107
108        internal TempDirectory TempUserDataDir { get; set; }
109
110        /// <summary>
111        /// Gets Base process current state.
112        /// </summary>
113        protected State CurrentState { get => _currentState; }
114
115        #endregion
116
117        #region Public methods
118
119        /// <summary>
120        /// Asynchronously starts Base process.
121        /// </summary>
122        /// <returns></returns>
123        public Task StartAsync() => _currentState.StartAsync(this);
124
125        /// <summary>
126        /// Asynchronously waits for graceful Base process exit within a given timeout period.
127        /// Kills the Base process if it has not exited within this period.
128        /// </summary>
129        /// <param name="timeout">The maximum waiting time for a graceful process exit.</param>
130        /// <returns></returns>
131        public Task EnsureExitAsync(TimeSpan? timeout) => timeout.HasValue
132            ? _currentState.ExitAsync(this, timeout.Value)
133            : _currentState.KillAsync(this);
134
135        /// <summary>
136        /// Asynchronously kills Base process.
137        /// </summary>
138        /// <returns></returns>
139        public Task KillAsync() => _currentState.KillAsync(this);
140
141        /// <summary>
142        /// Waits for Base process exit within a given timeout.
143        /// </summary>
144        /// <param name="timeout">The maximum wait period.</param>
145        /// <returns><c>true</c> if Base process has exited within the given <paramref name="timeout"/>,
146        /// or <c>false</c> otherwise.</returns>
147        public async Task<bool> WaitForExitAsync(TimeSpan? timeout)
148        {
149            if (timeout.HasValue)
150            {
151                bool taskCompleted = true;
152                await _exitCompletionSource.Task.WithTimeout(
153                    () =>
154                    {
155                        taskCompleted = false;
156                    }, timeout.Value).ConfigureAwait(false);
157                return taskCompleted;
158            }
159
160            await _exitCompletionSource.Task.ConfigureAwait(false);
161            return true;
162        }
163
164        #endregion
165
166        #region Private methods
167
168        /// <summary>
169        /// Set Env Variables
170        /// </summary>
171        /// <param name="environment">The environment.</param>
172        /// <param name="customEnv">The customEnv.</param>
173        /// <param name="realEnv">The realEnv.</param>
174        protected static void SetEnvVariables(IDictionary<string, string> environment, IDictionary<string, string> customEnv, IDictionary realEnv)
175        {
176            foreach (DictionaryEntry item in realEnv)
177            {
178                environment[item.Key.ToString()] = item.Value.ToString();
179            }
180
181            if (customEnv != null)
182            {
183                foreach (var item in customEnv)
184                {
185                    environment[item.Key] = item.Value;
186                }
187            }
188        }
189
190        #endregion
191
192        #region State machine
193
194        /// <summary>
195        /// Represents state machine for Base process instances. The happy path runs along the
196        /// following state transitions: <see cref="Initial"/>
197        /// -> <see cref="Starting"/>
198        /// -> <see cref="Started"/>
199        /// -> <see cref="Exiting"/>
200        /// -> <see cref="Exited"/>.
201        /// -> <see cref="Disposed"/>.
202        /// </summary>
203        /// <remarks>
204        /// <para>
205        /// This state machine implements the following state transitions:
206        /// <code>
207        /// State     Event              Target State Action
208        /// ======== =================== ============ ==========================================================
209        /// Initial  --StartAsync------> Starting     Start process and wait for endpoint
210        /// Initial  --ExitAsync-------> Exited       Cleanup temp user data
211        /// Initial  --KillAsync-------> Exited       Cleanup temp user data
212        /// Initial  --Dispose---------> Disposed     Cleanup temp user data
213        /// Starting --StartAsync------> Starting     -
214        /// Starting --ExitAsync-------> Exiting      Wait for process exit
215        /// Starting --KillAsync-------> Killing      Kill process
216        /// Starting --Dispose---------> Disposed     Kill process; Cleanup temp user data;  throw ObjectDisposedException on outstanding async operations;
217        /// Starting --endpoint ready--> Started      Complete StartAsync successfully; Log process start
218        /// Starting --process exit----> Exited       Complete StartAsync with exception; Cleanup temp user data
219        /// Started  --StartAsync------> Started      -
220        /// Started  --EnsureExitAsync-> Exiting      Start exit timer; Log process exit
221        /// Started  --KillAsync-------> Killing      Kill process; Log process exit
222        /// Started  --Dispose---------> Disposed     Kill process; Log process exit; Cleanup temp user data; throw ObjectDisposedException on outstanding async operations;
223        /// Started  --process exit----> Exited       Log process exit; Cleanup temp user data
224        /// Exiting  --StartAsync------> Exiting      - (StartAsync throws InvalidOperationException)
225        /// Exiting  --ExitAsync-------> Exiting      -
226        /// Exiting  --KillAsync-------> Killing      Kill process
227        /// Exiting  --Dispose---------> Disposed     Kill process; Cleanup temp user data; throw ObjectDisposedException on outstanding async operations;
228        /// Exiting  --exit timeout----> Killing      Kill process
229        /// Exiting  --process exit----> Exited       Cleanup temp user data; complete outstanding async operations;
230        /// Killing  --StartAsync------> Killing      - (StartAsync throws InvalidOperationException)
231        /// Killing  --KillAsync-------> Killing      -
232        /// Killing  --Dispose---------> Disposed     Cleanup temp user data; throw ObjectDisposedException on outstanding async operations;
233        /// Killing  --process exit----> Exited       Cleanup temp user data; complete outstanding async operations;
234        /// Exited   --StartAsync------> Killing      - (StartAsync throws InvalidOperationException)
235        /// Exited   --KillAsync-------> Exited       -
236        /// Exited   --Dispose---------> Disposed     -
237        /// Disposed --StartAsync------> Disposed     -
238        /// Disposed --KillAsync-------> Disposed     -
239        /// Disposed --Dispose---------> Disposed     -
240        /// </code>
241        /// </para>
242        /// </remarks>
243        protected abstract class State
244        {
245            #region Predefined states
246
247            /// <summary>
248            /// Set initial state.
249            /// </summary>
250            public static readonly State Initial = new InitialState();
251            private static readonly StartingState Starting = new StartingState();
252            private static readonly StartedState Started = new StartedState();
253            private static readonly ExitingState Exiting = new ExitingState();
254            private static readonly KillingState Killing = new KillingState();
255            private static readonly ExitedState Exited = new ExitedState();
256            private static readonly DisposedState Disposed = new DisposedState();
257
258            #endregion
259
260            #region Properties
261
262            /// <summary>
263            /// Get If process exists.
264            /// </summary>
265            public bool IsExiting => this == Killing || this == Exiting;
266            /// <summary>
267            /// Get If process is exited.
268            /// </summary>
269            public bool IsExited => this == Exited || this == Disposed;
270
271            #endregion
272
273            #region Methods
274
275            /// <summary>
276            /// Attempts thread-safe transitions from a given state to this state.
277            /// </summary>
278            /// <param name="p">The Base process</param>
279            /// <param name="fromState">The state from which state transition takes place</param>
280            /// <returns>Returns <c>true</c> if transition is successful, or <c>false</c> if transition
281            /// cannot be made because current state does not equal <paramref name="fromState"/>.</returns>
282            protected bool TryEnter(LauncherBase p, State fromState)
283            {
284                if (Interlocked.CompareExchange(ref p._currentState, this, fromState) == fromState)
285                {
286                    fromState.Leave(p);
287                    return true;
288                }
289
290                return false;
291            }
292
293            /// <summary>
294            /// Notifies that state machine is about to transition to another state.
295            /// </summary>
296            /// <param name="p">The Base process</param>
297            protected virtual void Leave(LauncherBase p)
298            {
299            }
300
301            /// <summary>
302            /// Handles process start request.
303            /// </summary>
304            /// <param name="p">The Base process</param>
305            /// <returns></returns>
306            public virtual Task StartAsync(LauncherBase p) => Task.FromException(InvalidOperation("start"));
307
308            /// <summary>
309            /// Handles process exit request.
310            /// </summary>
311            /// <param name="p">The Base process</param>
312            /// <param name="timeout">The maximum waiting time for a graceful process exit.</param>
313            /// <returns></returns>
314            public virtual Task ExitAsync(LauncherBase p, TimeSpan timeout) => Task.FromException(InvalidOperation("exit"));
315
316            /// <summary>
317            /// Handles process kill request.
318            /// </summary>
319            /// <param name="p">The Base process</param>
320            /// <returns></returns>
321            public virtual Task KillAsync(LauncherBase p) => Task.FromException(InvalidOperation("kill"));
322
323            /// <summary>
324            /// Handles wait for process exit request.
325            /// </summary>
326            /// <param name="p">The Base process</param>
327            /// <returns></returns>
328            public virtual Task WaitForExitAsync(LauncherBase p) => p._exitCompletionSource.Task;
329
330            /// <summary>
331            /// Handles disposal of process and temporary user directory
332            /// </summary>
333            /// <param name="p"></param>
334            public virtual void Dispose(LauncherBase p) => Disposed.EnterFrom(p, this);
335
336            /// <inheritdoc />
337            public override string ToString()
338            {
339                string name = GetType().Name;
340                return name.Substring(0, name.Length - "State".Length);
341            }
342
343            private Exception InvalidOperation(string operationName)
344                => new InvalidOperationException($"Cannot {operationName} in state {this}");
345
346            /// <summary>
347            /// Kills process if it is still alive.
348            /// </summary>
349            /// <param name="p"></param>
350            private static void Kill(LauncherBase p)
351            {
352                try
353                {
354                    if (!p.Process.HasExited)
355                    {
356                        p.Process.Kill();
357                    }
358                }
359                catch (InvalidOperationException)
360                {
361                    // Ignore
362                }
363            }
364
365            #endregion
366
367            #region Concrete state classes
368
369            private class InitialState : State
370            {
371                public override Task StartAsync(LauncherBase p) => Starting.EnterFromAsync(p, this);
372
373                public override Task ExitAsync(LauncherBase p, TimeSpan timeout)
374                {
375                    Exited.EnterFrom(p, this);
376                    return Task.CompletedTask;
377                }
378
379                public override Task KillAsync(LauncherBase p)
380                {
381                    Exited.EnterFrom(p, this);
382                    return Task.CompletedTask;
383                }
384
385                public override Task WaitForExitAsync(LauncherBase p) => Task.FromException(InvalidOperation("wait for exit"));
386            }
387
388            private class StartingState : State
389            {
390                public Task EnterFromAsync(LauncherBase p, State fromState)
391                {
392                    if (!TryEnter(p, fromState))
393                    {
394                        // Delegate StartAsync to current state, because it has already changed since
395                        // transition to this state was initiated.
396                        return p._currentState.StartAsync(p);
397                    }
398
399                    return StartCoreAsync(p);
400                }
401
402                public override Task StartAsync(LauncherBase p) => p._startCompletionSource.Task;
403
404                public override Task ExitAsync(LauncherBase p, TimeSpan timeout) => Exiting.EnterFromAsync(p, this, timeout);
405
406                public override Task KillAsync(LauncherBase p) => Killing.EnterFromAsync(p, this);
407
408                public override void Dispose(LauncherBase p)
409                {
410                    p._startCompletionSource.TrySetException(new ObjectDisposedException(p.ToString()));
411                    base.Dispose(p);
412                }
413
414                private async Task StartCoreAsync(LauncherBase p)
415                {
416                    var output = new StringBuilder();
417
418                    void OnProcessDataReceivedWhileStarting(object sender, DataReceivedEventArgs e)
419                    {
420                        if (e.Data != null)
421                        {
422                            output.AppendLine(e.Data);
423                            var match = Regex.Match(e.Data, "^DevTools listening on (ws:\\/\\/.*)");
424                            if (match.Success)
425                            {
426                                p._startCompletionSource.TrySetResult(match.Groups[1].Value);
427                            }
428                        }
429                    }
430
431                    void OnProcessExitedWhileStarting(object sender, EventArgs e)
432                        => p._startCompletionSource.TrySetException(new ProcessException($"Failed to launch Base! {output}"));
433                    void OnProcessExited(object sender, EventArgs e) => Exited.EnterFrom(p, p._currentState);
434
435                    p.Process.ErrorDataReceived += OnProcessDataReceivedWhileStarting;
436                    p.Process.Exited += OnProcessExitedWhileStarting;
437                    p.Process.Exited += OnProcessExited;
438                    CancellationTokenSource cts = null;
439                    try
440                    {
441                        p.Process.Start();
442                        await Started.EnterFromAsync(p, this).ConfigureAwait(false);
443
444                        p.Process.BeginErrorReadLine();
445
446                        int timeout = p._options.Timeout;
447                        if (timeout > 0)
448                        {
449                            cts = new CancellationTokenSource(timeout);
450                            cts.Token.Register(() => p._startCompletionSource.TrySetException(
451                                new ProcessException($"Timed out after {timeout} ms while trying to connect to Base!")));
452                        }
453
454                        try
455                        {
456                            await p._startCompletionSource.Task.ConfigureAwait(false);
457                            await Started.EnterFromAsync(p, this).ConfigureAwait(false);
458                        }
459                        catch
460                        {
461                            await Killing.EnterFromAsync(p, this).ConfigureAwait(false);
462                            throw;
463                        }
464                    }
465                    finally
466                    {
467                        cts?.Dispose();
468                        p.Process.Exited -= OnProcessExitedWhileStarting;
469                        p.Process.ErrorDataReceived -= OnProcessDataReceivedWhileStarting;
470                    }
471                }
472            }
473
474            private class StartedState : State
475            {
476                public Task EnterFromAsync(LauncherBase p, State fromState)
477                {
478                    if (TryEnter(p, fromState))
479                    {
480                    }
481
482                    return Task.CompletedTask;
483                }
484
485                protected override void Leave(LauncherBase p) { }
486
487                public override Task StartAsync(LauncherBase p) => Task.CompletedTask;
488
489                public override Task ExitAsync(LauncherBase p, TimeSpan timeout) => Exiting.EnterFromAsync(p, this, timeout);
490
491                public override Task KillAsync(LauncherBase p) => Killing.EnterFromAsync(p, this);
492            }
493
494            private class ExitingState : State
495            {
496                public Task EnterFromAsync(LauncherBase p, State fromState, TimeSpan timeout)
497                    => !TryEnter(p, fromState) ? p._currentState.ExitAsync(p, timeout) : ExitAsync(p, timeout);
498
499                public override async Task ExitAsync(LauncherBase p, TimeSpan timeout)
500                {
501                    var waitForExitTask = WaitForExitAsync(p);
502                    await waitForExitTask.WithTimeout(
503                        async () =>
504                        {
505                            await Killing.EnterFromAsync(p, this).ConfigureAwait(false);
506                            await waitForExitTask.ConfigureAwait(false);
507                        },
508                        timeout,
509                        CancellationToken.None).ConfigureAwait(false);
510                }
511
512                public override Task KillAsync(LauncherBase p) => Killing.EnterFromAsync(p, this);
513            }
514
515            private class KillingState : State
516            {
517                public async Task EnterFromAsync(LauncherBase p, State fromState)
518                {
519                    if (!TryEnter(p, fromState))
520                    {
521                        // Delegate KillAsync to current state, because it has already changed since
522                        // transition to this state was initiated.
523                        await p._currentState.KillAsync(p).ConfigureAwait(false);
524                    }
525
526                    try
527                    {
528                        if (!p.Process.HasExited)
529                        {
530                            p.Process.Kill();
531                        }
532                    }
533                    catch (InvalidOperationException)
534                    {
535                        // Ignore
536                        return;
537                    }
538
539                    await WaitForExitAsync(p).ConfigureAwait(false);
540                }
541
542                public override Task ExitAsync(LauncherBase p, TimeSpan timeout) => WaitForExitAsync(p);
543
544                public override Task KillAsync(LauncherBase p) => WaitForExitAsync(p);
545            }
546
547            private class ExitedState : State
548            {
549                public void EnterFrom(LauncherBase p, State fromState)
550                {
551                    while (!TryEnter(p, fromState))
552                    {
553                        // Current state has changed since transition to this state was requested.
554                        // Therefore retry transition to this state from the current state. This ensures
555                        // that Leave() operation of current state is properly called.
556                        fromState = p._currentState;
557                        if (fromState == this)
558                        {
559                            return;
560                        }
561                    }
562
563                    p._exitCompletionSource.TrySetResult(true);
564                    p.TempUserDataDir?.Dispose();
565                }
566
567                public override Task ExitAsync(LauncherBase p, TimeSpan timeout) => Task.CompletedTask;
568
569                public override Task KillAsync(LauncherBase p) => Task.CompletedTask;
570
571                public override Task WaitForExitAsync(LauncherBase p) => Task.CompletedTask;
572            }
573
574            private class DisposedState : State
575            {
576                public void EnterFrom(LauncherBase p, State fromState)
577                {
578                    if (!TryEnter(p, fromState))
579                    {
580                        // Delegate Dispose to current state, because it has already changed since
581                        // transition to this state was initiated.
582                        p._currentState.Dispose(p);
583                    }
584                    else if (fromState != Exited)
585                    {
586                        Kill(p);
587
588                        p._exitCompletionSource.TrySetException(new ObjectDisposedException(p.ToString()));
589                        p.TempUserDataDir?.Dispose();
590                    }
591                }
592
593                public override Task StartAsync(LauncherBase p) => throw new ObjectDisposedException(p.ToString());
594
595                public override Task ExitAsync(LauncherBase p, TimeSpan timeout) => throw new ObjectDisposedException(p.ToString());
596
597                public override Task KillAsync(LauncherBase p) => throw new ObjectDisposedException(p.ToString());
598
599                public override void Dispose(LauncherBase p)
600                {
601                    // Nothing to do
602                }
603            }
604
605            #endregion
606        }
607
608        #endregion
609    }
610}
611
Full Screen

ChromiumProcess.cs

Source: ChromiumProcess.cs Github

copy
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Diagnostics;
5using System.Linq;
6using System.Text;
7using System.Text.RegularExpressions;
8using System.Threading;
9using System.Threading.Tasks;
10using Microsoft.Extensions.Logging;
11using PuppeteerSharp.Helpers;
12
13namespace PuppeteerSharp
14{
15    /// <summary>
16    /// Represents a Chromium process and any associated temporary user data directory that have created
17    /// by Puppeteer and therefore must be cleaned up when no longer needed.
18    /// </summary>
19    public class ChromiumProcess : IDisposable
20    {
21        #region Constants
22
23        internal static readonly string[] DefaultArgs = {
24            "--disable-background-networking",
25            "--disable-background-timer-throttling",
26            "--disable-breakpad",
27            "--disable-client-side-phishing-detection",
28            "--disable-default-apps",
29            "--disable-dev-shm-usage",
30            "--disable-extensions",
31            "--disable-features=site-per-process",
32            "--disable-hang-monitor",
33            "--disable-popup-blocking",
34            "--disable-prompt-on-repost",
35            "--disable-sync",
36            "--disable-translate",
37            "--metrics-recording-only",
38            "--no-first-run",
39            "--safebrowsing-disable-auto-update",
40            "--enable-automation",
41            "--password-store=basic",
42            "--use-mock-keychain"
43        };
44
45        private const string UserDataDirArgument = "--user-data-dir";
46
47        #endregion
48
49        #region Static fields
50
51        private static int _processCount;
52
53        #endregion
54
55        #region Instance fields
56
57        private readonly LaunchOptions _options;
58        private readonly TempDirectory _tempUserDataDir;
59        private readonly ILogger _logger;
60        private readonly TaskCompletionSource<string> _startCompletionSource = new TaskCompletionSource<string>();
61        private readonly TaskCompletionSource<bool> _exitCompletionSource = new TaskCompletionSource<bool>();
62        private State _currentState = State.Initial;
63
64        #endregion
65
66        #region Constructor
67
68        /// <summary>
69        /// Creates a new <see cref="ChromiumProcess"/> instance.
70        /// </summary>
71        /// <param name="chromiumExecutable">Full path of Chromium executable.</param>
72        /// <param name="options">Options for launching Chromium.</param>
73        /// <param name="loggerFactory">Logger factory</param>
74        public ChromiumProcess(string chromiumExecutable, LaunchOptions options, ILoggerFactory loggerFactory)
75        {
76            _options = options;
77            _logger = options.LogProcess
78                ? loggerFactory.CreateLogger<ChromiumProcess>()
79                : null;
80
81            List<string> chromiumArgs;
82            (chromiumArgs, _tempUserDataDir) = PrepareChromiumArgs(options);
83
84            Process = new Process
85            {
86                EnableRaisingEvents = true
87            };
88            Process.StartInfo.UseShellExecute = false;
89            Process.StartInfo.FileName = chromiumExecutable;
90            Process.StartInfo.Arguments = string.Join(" ", chromiumArgs);
91            Process.StartInfo.RedirectStandardError = true;
92
93            SetEnvVariables(Process.StartInfo.Environment, options.Env, Environment.GetEnvironmentVariables());
94
95            if (options.DumpIO)
96            {
97                Process.ErrorDataReceived += (sender, e) => Console.Error.WriteLine(e.Data);
98            }
99        }
100
101        #endregion
102
103        #region Dispose
104
105        /// <summary>
106        /// Finalizer.
107        /// </summary>
108        ~ChromiumProcess()
109        {
110            Dispose(false);
111        }
112
113        /// <inheritdoc />
114        public void Dispose()
115        {
116            GC.SuppressFinalize(this);
117            Dispose(true);
118        }
119
120        /// <summary>
121        /// Disposes Chromium process and any temporary user directory.
122        /// </summary>
123        /// <param name="disposing">Indicates whether disposal was initiated by <see cref="Dispose()"/> operation.</param>
124        protected virtual void Dispose(bool disposing) => _currentState.Dispose(this);
125
126        #endregion
127
128        #region Properties
129
130        /// <summary>
131        /// Gets Chromium process details.
132        /// </summary>
133        public Process Process { get; }
134
135        /// <summary>
136        /// Gets Chromium endpoint.
137        /// </summary>
138        public string EndPoint => _startCompletionSource.Task.IsCompleted
139            ? _startCompletionSource.Task.Result
140            : null;
141
142        /// <summary>
143        /// Indicates whether Chromium process is exiting. 
144        /// </summary>
145        public bool IsExiting => _currentState.IsExiting;
146
147        /// <summary>
148        /// Indicates whether Chromium process has exited. 
149        /// </summary>
150        public bool HasExited => _currentState.IsExited;
151
152        #endregion
153
154        #region Public methods
155
156        /// <summary>
157        /// Asynchronously starts Chromium process.
158        /// </summary>
159        /// <returns></returns>
160        public Task StartAsync() => _currentState.StartAsync(this);
161
162        /// <summary>
163        /// Asynchronously waits for graceful Chromium process exit within a given timeout period.
164        /// Kills the Chromium process if it has not exited within this period.
165        /// </summary>
166        /// <param name="timeout">The maximum waiting time for a graceful process exit.</param>
167        /// <returns></returns>
168        public Task EnsureExitAsync(TimeSpan? timeout) => timeout.HasValue
169            ? _currentState.ExitAsync(this, timeout.Value)
170            : _currentState.KillAsync(this);
171
172        /// <summary>
173        /// Asynchronously kills Chromium process.
174        /// </summary>
175        /// <returns></returns>
176        public Task KillAsync() => _currentState.KillAsync(this);
177
178        /// <summary>
179        /// Waits for Chromium process exit within a given timeout.
180        /// </summary>
181        /// <param name="timeout">The maximum wait period.</param>
182        /// <returns><c>true</c> if Chromium process has exited within the given <paramref name="timeout"/>,
183        /// or <c>false</c> otherwise.</returns>
184        public async Task<bool> WaitForExitAsync(TimeSpan? timeout)
185        {
186            if (timeout.HasValue)
187            {
188                var completedTask = await Task.WhenAny(_exitCompletionSource.Task, Task.Delay(timeout.Value)).ConfigureAwait(false);
189                return completedTask == _exitCompletionSource.Task;
190            }
191
192            await _exitCompletionSource.Task.ConfigureAwait(false);
193            return true;
194        }
195
196        /// <inheritdoc />
197        public override string ToString() => $"Chromium process; EndPoint={EndPoint}; State={_currentState}";
198
199        #endregion
200
201        #region Private methods
202
203        private static (List<string> chromiumArgs, TempDirectory tempUserDataDir) PrepareChromiumArgs(LaunchOptions options)
204        {
205
206            var chromiumArgs = new List<string>();
207
208            if (!options.IgnoreDefaultArgs)
209            {
210                chromiumArgs.AddRange(GetDefaultArgs(options));
211            }
212            else if (options.IgnoredDefaultArgs?.Length > 0)
213            {
214                chromiumArgs.AddRange(GetDefaultArgs(options).Except(options.IgnoredDefaultArgs));
215            }
216            else
217            {
218                chromiumArgs.AddRange(options.Args);
219            }
220
221            TempDirectory tempUserDataDir = null;
222
223            if (!chromiumArgs.Any(argument => argument.StartsWith("--remote-debugging-", StringComparison.Ordinal)))
224            {
225                chromiumArgs.Add("--remote-debugging-port=0");
226            }
227
228            var userDataDirOption = chromiumArgs.FirstOrDefault(i => i.StartsWith(UserDataDirArgument, StringComparison.Ordinal));
229            if (string.IsNullOrEmpty(userDataDirOption))
230            {
231                tempUserDataDir = new TempDirectory();
232                chromiumArgs.Add($"{UserDataDirArgument}={tempUserDataDir.Path.Quote()}");
233            }
234
235            return (chromiumArgs, tempUserDataDir);
236        }
237
238        internal static string[] GetDefaultArgs(LaunchOptions options)
239        {
240            var chromeArguments = new List<string>(DefaultArgs);
241
242            if (!string.IsNullOrEmpty(options.UserDataDir))
243            {
244                chromeArguments.Add($"{UserDataDirArgument}={options.UserDataDir.Quote()}");
245            }
246            if (options.Devtools)
247            {
248                chromeArguments.Add("--auto-open-devtools-for-tabs");
249            }
250            if (options.Headless)
251            {
252                chromeArguments.AddRange(new[]{
253                    "--headless",
254                    "--hide-scrollbars",
255                    "--mute-audio"
256                });
257                if (BrowserFetcher.GetCurrentPlatform() == Platform.Win32)
258                {
259                    chromeArguments.Add("--disable-gpu");
260                }
261            }
262
263            if (options.Args.All(arg => arg.StartsWith("-", StringComparison.Ordinal)))
264            {
265                chromeArguments.Add("about:blank");
266            }
267
268            chromeArguments.AddRange(options.Args);
269            return chromeArguments.ToArray();
270        }
271
272        private static void SetEnvVariables(IDictionary<string, string> environment, IDictionary<string, string> customEnv, IDictionary realEnv)
273        {
274            foreach (DictionaryEntry item in realEnv)
275            {
276                environment[item.Key.ToString()] = item.Value.ToString();
277            }
278
279            if (customEnv != null)
280            {
281                foreach (var item in customEnv)
282                {
283                    environment[item.Key] = item.Value;
284                }
285            }
286        }
287
288        #endregion
289
290        #region State machine
291
292        /// <summary>
293        /// Represents state machine for Chromium process instances. The happy path runs along the
294        /// following state transitions: <see cref="Initial"/>
295        /// -> <see cref="Starting"/>
296        /// -> <see cref="Started"/>
297        /// -> <see cref="Exiting"/>
298        /// -> <see cref="Exited"/>.
299        /// -> <see cref="Disposed"/>.
300        /// </summary>
301        /// <remarks>
302        /// <para>
303        /// This state machine implements the following state transitions:
304        /// <code>
305        /// State     Event              Target State Action
306        /// ======== =================== ============ ==========================================================
307        /// Initial  --StartAsync------> Starting     Start process and wait for endpoint
308        /// Initial  --ExitAsync-------> Exited       Cleanup temp user data
309        /// Initial  --KillAsync-------> Exited       Cleanup temp user data
310        /// Initial  --Dispose---------> Disposed     Cleanup temp user data
311        /// Starting --StartAsync------> Starting     -
312        /// Starting --ExitAsync-------> Exiting      Wait for process exit
313        /// Starting --KillAsync-------> Killing      Kill process
314        /// Starting --Dispose---------> Disposed     Kill process; Cleanup temp user data;  throw ObjectDisposedException on outstanding async operations;
315        /// Starting --endpoint ready--> Started      Complete StartAsync successfully; Log process start
316        /// Starting --process exit----> Exited       Complete StartAsync with exception; Cleanup temp user data
317        /// Started  --StartAsync------> Started      -
318        /// Started  --EnsureExitAsync-> Exiting      Start exit timer; Log process exit
319        /// Started  --KillAsync-------> Killing      Kill process; Log process exit
320        /// Started  --Dispose---------> Disposed     Kill process; Log process exit; Cleanup temp user data; throw ObjectDisposedException on outstanding async operations;
321        /// Started  --process exit----> Exited       Log process exit; Cleanup temp user data
322        /// Exiting  --StartAsync------> Exiting      - (StartAsync throws InvalidOperationException)
323        /// Exiting  --ExitAsync-------> Exiting      -
324        /// Exiting  --KillAsync-------> Killing      Kill process
325        /// Exiting  --Dispose---------> Disposed     Kill process; Cleanup temp user data; throw ObjectDisposedException on outstanding async operations;
326        /// Exiting  --exit timeout----> Killing      Kill process
327        /// Exiting  --process exit----> Exited       Cleanup temp user data; complete outstanding async operations;
328        /// Killing  --StartAsync------> Killing      - (StartAsync throws InvalidOperationException)
329        /// Killing  --KillAsync-------> Killing      -
330        /// Killing  --Dispose---------> Disposed     Cleanup temp user data; throw ObjectDisposedException on outstanding async operations;
331        /// Killing  --process exit----> Exited       Cleanup temp user data; complete outstanding async operations;
332        /// Exited   --StartAsync------> Killing      - (StartAsync throws InvalidOperationException)
333        /// Exited   --KillAsync-------> Exited       -
334        /// Exited   --Dispose---------> Disposed     -
335        /// Disposed --StartAsync------> Disposed     -
336        /// Disposed --KillAsync-------> Disposed     -
337        /// Disposed --Dispose---------> Disposed     -
338        /// </code>
339        /// </para>
340        /// </remarks>
341        private abstract class State
342        {
343            #region Predefined states
344
345            public static readonly State Initial = new InitialState();
346            private static readonly StartingState Starting = new StartingState();
347            private static readonly StartedState Started = new StartedState();
348            private static readonly ExitingState Exiting = new ExitingState();
349            private static readonly KillingState Killing = new KillingState();
350            private static readonly ExitedState Exited = new ExitedState();
351            private static readonly DisposedState Disposed = new DisposedState();
352
353            #endregion
354
355            #region Properties
356
357            public bool IsExiting => this == Killing || this == Exiting;
358            public bool IsExited => this == Exited || this == Disposed;
359
360            #endregion
361
362            #region Methods
363
364            /// <summary>
365            /// Attempts thread-safe transitions from a given state to this state.
366            /// </summary>
367            /// <param name="p">The Chromium process</param>
368            /// <param name="fromState">The state from which state transition takes place</param>
369            /// <returns>Returns <c>true</c> if transition is successful, or <c>false</c> if transition
370            /// cannot be made because current state does not equal <paramref name="fromState"/>.</returns>
371            protected bool TryEnter(ChromiumProcess p, State fromState)
372            {
373                if (Interlocked.CompareExchange(ref p._currentState, this, fromState) == fromState)
374                {
375                    fromState.Leave(p);
376                    return true;
377                }
378
379                return false;
380            }
381
382            /// <summary>
383            /// Notifies that state machine is about to transition to another state.
384            /// </summary>
385            /// <param name="p">The Chromium process</param>
386            protected virtual void Leave(ChromiumProcess p)
387            { }
388
389            /// <summary>
390            /// Handles process start request.
391            /// </summary>
392            /// <param name="p">The Chromium process</param>
393            /// <returns></returns>
394            public virtual Task StartAsync(ChromiumProcess p) => Task.FromException(InvalidOperation("start"));
395
396            /// <summary>
397            /// Handles process exit request.
398            /// </summary>
399            /// <param name="p">The Chromium process</param>
400            /// <param name="timeout">The maximum waiting time for a graceful process exit.</param>
401            /// <returns></returns>
402            public virtual Task ExitAsync(ChromiumProcess p, TimeSpan timeout) => Task.FromException(InvalidOperation("exit"));
403
404            /// <summary>
405            /// Handles process kill request.
406            /// </summary>
407            /// <param name="p">The Chromium process</param>
408            /// <returns></returns>
409            public virtual Task KillAsync(ChromiumProcess p) => Task.FromException(InvalidOperation("kill"));
410
411            /// <summary>
412            /// Handles wait for process exit request.
413            /// </summary>
414            /// <param name="p">The Chromium process</param>
415            /// <returns></returns>
416            public virtual Task WaitForExitAsync(ChromiumProcess p) => p._exitCompletionSource.Task;
417
418            /// <summary>
419            /// Handles disposal of process and temporary user directory
420            /// </summary>
421            /// <param name="p"></param>
422            public virtual void Dispose(ChromiumProcess p) => Disposed.EnterFrom(p, this);
423
424            public override string ToString()
425            {
426                var name = GetType().Name;
427                return name.Substring(0, name.Length - "State".Length);
428            }
429
430            private Exception InvalidOperation(string operationName)
431                => new InvalidOperationException($"Cannot {operationName} in state {this}");
432
433            /// <summary>
434            /// Kills process if it is still alive.
435            /// </summary>
436            /// <param name="p"></param>
437            private static void Kill(ChromiumProcess p)
438            {
439                try
440                {
441                    if (!p.Process.HasExited)
442                    {
443                        p.Process.Kill();
444                    }
445                }
446                catch (InvalidOperationException)
447                {
448                    // Ignore
449                }
450            }
451
452            #endregion
453
454            #region Concrete state classes
455
456            private class InitialState : State
457            {
458                public override Task StartAsync(ChromiumProcess p) => Starting.EnterFromAsync(p, this);
459
460                public override Task ExitAsync(ChromiumProcess p, TimeSpan timeout)
461                {
462                    Exited.EnterFrom(p, this);
463                    return Task.CompletedTask;
464                }
465
466                public override Task KillAsync(ChromiumProcess p)
467                {
468                    Exited.EnterFrom(p, this);
469                    return Task.CompletedTask;
470                }
471
472                public override Task WaitForExitAsync(ChromiumProcess p) => Task.FromException(InvalidOperation("wait for exit"));
473            }
474
475            private class StartingState : State
476            {
477                public Task EnterFromAsync(ChromiumProcess p, State fromState)
478                {
479                    if (!TryEnter(p, fromState))
480                    {
481                        // Delegate StartAsync to current state, because it has already changed since
482                        // transition to this state was initiated.
483                        return p._currentState.StartAsync(p);
484                    }
485
486                    return StartCoreAsync(p);
487                }
488
489                public override Task StartAsync(ChromiumProcess p) => p._startCompletionSource.Task;
490
491                public override Task ExitAsync(ChromiumProcess p, TimeSpan timeout) => Exiting.EnterFromAsync(p, this, timeout);
492
493                public override Task KillAsync(ChromiumProcess p) => Killing.EnterFromAsync(p, this);
494
495                public override void Dispose(ChromiumProcess p)
496                {
497                    p._startCompletionSource.TrySetException(new ObjectDisposedException(p.ToString()));
498                    base.Dispose(p);
499                }
500
501                private async Task StartCoreAsync(ChromiumProcess p)
502                {
503                    var output = new StringBuilder();
504
505                    void OnProcessDataReceivedWhileStarting(object sender, DataReceivedEventArgs e)
506                    {
507                        if (e.Data != null)
508                        {
509                            output.AppendLine(e.Data);
510                            var match = Regex.Match(e.Data, "^DevTools listening on (ws:\\/\\/.*)");
511                            if (match.Success)
512                            {
513                                p._startCompletionSource.SetResult(match.Groups[1].Value);
514                            }
515                        }
516                    }
517                    void OnProcessExitedWhileStarting(object sender, EventArgs e)
518                    {
519                        p._startCompletionSource.SetException(new ChromiumProcessException($"Failed to launch Chromium! {output}"));
520                    }
521                    void OnProcessExited(object sender, EventArgs e)
522                    {
523                        Exited.EnterFrom(p, p._currentState);
524                    }
525
526                    p.Process.ErrorDataReceived += OnProcessDataReceivedWhileStarting;
527                    p.Process.Exited += OnProcessExitedWhileStarting;
528                    p.Process.Exited += OnProcessExited;
529                    CancellationTokenSource cts = null;
530                    try
531                    {
532                        p.Process.Start();
533                        await Started.EnterFromAsync(p, this).ConfigureAwait(false);
534
535                        p.Process.BeginErrorReadLine();
536
537                        var timeout = p._options.Timeout;
538                        if (timeout > 0)
539                        {
540                            cts = new CancellationTokenSource(timeout);
541                            cts.Token.Register(() => p._startCompletionSource.TrySetException(
542                                new ChromiumProcessException($"Timed out after {timeout} ms while trying to connect to Chromium!")));
543                        }
544
545                        try
546                        {
547                            await p._startCompletionSource.Task.ConfigureAwait(false);
548                            await Started.EnterFromAsync(p, this).ConfigureAwait(false);
549                        }
550                        catch
551                        {
552                            await Killing.EnterFromAsync(p, this).ConfigureAwait(false);
553                            throw;
554                        }
555                    }
556                    finally
557                    {
558                        cts?.Dispose();
559                        p.Process.Exited -= OnProcessExitedWhileStarting;
560                        p.Process.ErrorDataReceived -= OnProcessDataReceivedWhileStarting;
561                    }
562                }
563            }
564
565            private class StartedState : State
566            {
567                public Task EnterFromAsync(ChromiumProcess p, State fromState)
568                {
569                    if (TryEnter(p, fromState))
570                    {
571                        // Process has not exited or been killed since transition to this state was initiated
572                        LogProcessCount(p, Interlocked.Increment(ref _processCount));
573                    }
574                    return Task.CompletedTask;
575                }
576
577                protected override void Leave(ChromiumProcess p)
578                    => LogProcessCount(p, Interlocked.Decrement(ref _processCount));
579
580                public override Task StartAsync(ChromiumProcess p) => Task.CompletedTask;
581
582                public override Task ExitAsync(ChromiumProcess p, TimeSpan timeout) => Exiting.EnterFromAsync(p, this, timeout);
583
584                public override Task KillAsync(ChromiumProcess p) => Killing.EnterFromAsync(p, this);
585
586                private static void LogProcessCount(ChromiumProcess p, int processCount)
587                {
588                    try
589                    {
590                        p._logger?.LogInformation("Process Count: {ProcessCount}", processCount);
591                    }
592                    catch
593                    {
594                        // Prevent logging exception from causing havoc
595                    }
596                }
597            }
598
599            private class ExitingState : State
600            {
601                public Task EnterFromAsync(ChromiumProcess p, State fromState, TimeSpan timeout)
602                {
603                    return !TryEnter(p, fromState) ? p._currentState.ExitAsync(p, timeout) : ExitAsync(p, timeout);
604                }
605
606                public override async Task ExitAsync(ChromiumProcess p, TimeSpan timeout)
607                {
608                    var timeoutTask = Task.Delay(timeout);
609                    var waitForExitTask = WaitForExitAsync(p);
610                    var completedTask = await Task.WhenAny(waitForExitTask, timeoutTask).ConfigureAwait(false);
611                    if (completedTask == timeoutTask)
612                    {
613                        await Killing.EnterFromAsync(p, this).ConfigureAwait(false);
614                        await waitForExitTask.ConfigureAwait(false);
615                    }
616                }
617
618                public override Task KillAsync(ChromiumProcess p) => Killing.EnterFromAsync(p, this);
619            }
620
621            private class KillingState : State
622            {
623                public Task EnterFromAsync(ChromiumProcess p, State fromState)
624                {
625                    if (!TryEnter(p, fromState))
626                    {
627                        // Delegate KillAsync to current state, because it has already changed since
628                        // transition to this state was initiated.
629                        return p._currentState.KillAsync(p);
630                    }
631
632                    try
633                    {
634                        if (!p.Process.HasExited)
635                        {
636                            p.Process.Kill();
637                        }
638                    }
639                    catch (InvalidOperationException)
640                    {
641                        // Ignore
642                    }
643
644                    return WaitForExitAsync(p);
645                }
646
647                public override Task ExitAsync(ChromiumProcess p, TimeSpan timeout) => WaitForExitAsync(p);
648
649                public override Task KillAsync(ChromiumProcess p) => WaitForExitAsync(p);
650            }
651
652            private class ExitedState : State
653            {
654                public void EnterFrom(ChromiumProcess p, State fromState)
655                {
656                    while (!TryEnter(p, fromState))
657                    {
658                        // Current state has changed since transition to this state was requested.
659                        // Therefore retry transition to this state from the current state. This ensures
660                        // that Leave() operation of current state is properly called.
661                        fromState = p._currentState;
662                        if (fromState == this)
663                        {
664                            return;
665                        }
666                    }
667
668                    p._exitCompletionSource.TrySetResult(true);
669                    p._tempUserDataDir?.Dispose();
670                }
671
672                public override Task ExitAsync(ChromiumProcess p, TimeSpan timeout) => Task.CompletedTask;
673
674                public override Task KillAsync(ChromiumProcess p) => Task.CompletedTask;
675
676                public override Task WaitForExitAsync(ChromiumProcess p) => Task.CompletedTask;
677            }
678
679            private class DisposedState : State
680            {
681                public void EnterFrom(ChromiumProcess p, State fromState)
682                {
683                    if (!TryEnter(p, fromState))
684                    {
685                        // Delegate Dispose to current state, because it has already changed since
686                        // transition to this state was initiated.
687                        p._currentState.Dispose(p);
688                    }
689                    else if (fromState != Exited)
690                    {
691                        Kill(p);
692
693                        p._exitCompletionSource.TrySetException(new ObjectDisposedException(p.ToString()));
694                        p._tempUserDataDir?.Dispose();
695                    }
696                }
697
698                public override Task StartAsync(ChromiumProcess p) => throw new ObjectDisposedException(p.ToString());
699
700                public override Task ExitAsync(ChromiumProcess p, TimeSpan timeout) => throw new ObjectDisposedException(p.ToString());
701
702                public override Task KillAsync(ChromiumProcess p) => throw new ObjectDisposedException(p.ToString());
703
704                public override void Dispose(ChromiumProcess p)
705                {
706                    // Nothing to do
707                }
708            }
709
710            #endregion
711        }
712
713        #endregion
714    }
715}
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Trigger KillAsync code on LambdaTest Cloud Grid

Execute automation tests with KillAsync on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)