'C# How to continue running JavaScript code after a redirect or refresh by using CefSharp

I need programmatically make 3 actions on the website

  1. filling search input
  2. pressing search button

(after that, a request occurs and the page returns me the search result)

  1. clicking on the first link

if I put action 1 and 2 in button 1:

private void Button1_Click(object sender, EventArgs e)
{
string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'' + ";" 
+ "document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click();";
var task1 = chrome.EvaluateScriptAsync(jsScript1);
task1.Wait();
}

And action 3 in button 2:

private void Button2_Click(object sender, EventArgs e)
{
   string jsScript3 = "document.getElementsByTagName('a')[1].click();";
   var task3 = chrome.EvaluateScriptAsync(jsScript3);
   task3.Wait();
}

Everything works perfectly. But I want to do all these actions inside one button.

I found the following solution:

private async void ButtonFind_Click(object sender, EventArgs e)
        {
//Action 1 & 2
string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'' + ";" 
+ "document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click();";
await chrome.EvaluateScriptAsync(jsScript1);



            //Action3
            Thread.Sleep(1000); //it is necessary to set exactly 1 seconds
            string jsScript3 = "document.getElementsByTagName('a')[2].click();";
            await chrome.EvaluateScriptAsync(jsScript3);

        }

I have to use Thread.Sleep(1000); because in JavaScript redirect or clicking on search button destroying all scripts currently running in the browser. But I think using Thread.Sleep or Task.Delay its not a good idea. How else can I change my code without using Thread.Sleep/Task.Delay?



Solution 1:[1]

You can use something like the following to wait for the page load

// create a static class for the extension method 
public static Task<LoadUrlAsyncResponse> WaitForLoadAsync(this IWebBrowser browser)
{
    var tcs = new TaskCompletionSource<LoadUrlAsyncResponse>(TaskCreationOptions.RunContinuationsAsynchronously);

    EventHandler<LoadErrorEventArgs> loadErrorHandler = null;
    EventHandler<LoadingStateChangedEventArgs> loadingStateChangeHandler = null;

    loadErrorHandler = (sender, args) =>
    {
        //Actions that trigger a download will raise an aborted error.
        //Generally speaking Aborted is safe to ignore
        if (args.ErrorCode == CefErrorCode.Aborted)
        {
            return;
        }

        //If LoadError was called then we'll remove both our handlers
        //as we won't need to capture LoadingStateChanged, we know there
        //was an error
        browser.LoadError -= loadErrorHandler;
        browser.LoadingStateChanged -= loadingStateChangeHandler;

        tcs.TrySetResult(new LoadUrlAsyncResponse(args.ErrorCode, -1));
    };

    loadingStateChangeHandler = (sender, args) =>
    {
        //Wait for while page to finish loading not just the first frame
        if (!args.IsLoading)
        {
            browser.LoadError -= loadErrorHandler;
            browser.LoadingStateChanged -= loadingStateChangeHandler;
            var host = args.Browser.GetHost();

            var navEntry = host?.GetVisibleNavigationEntry();

            int statusCode = navEntry?.HttpStatusCode ?? -1;

            //By default 0 is some sort of error, we map that to -1
            //so that it's clearer that something failed.
            if (statusCode == 0)
            {
                statusCode = -1;
            }

            tcs.TrySetResult(new LoadUrlAsyncResponse(statusCode == -1 ? CefErrorCode.Failed : CefErrorCode.None, statusCode));
        }
    };

    browser.LoadingStateChanged += loadingStateChangeHandler;
    browser.LoadError += loadErrorHandler;

    return tcs.Task;
}

// usage example 
await Task.WhenAll(chrome.WaitForLoadAsync(), chrome.EvaluateScriptAsync(script));

Solution 2:[2]

You never must use Sleep in that way. The Web maybe take more time or less and your code fail since in a while. You must work always in async mode. If you want, you can put a timeout in case of error but in normal mode, always wait until you get that you want.

For example, click the button and set timer to wait your links appear. Then, navigate to first link.

In other cases, when a real navigation occurs and you lost your JavaScript context, you can use the bound object to comunicate your C# application and the browser. Doing this you can query from your JavaScript what are you doing and, from JavaScript, give feedback to your C# code. It's a way to persist the state.

A typical sample is when you fill some forms and click continue, continue... navigating in several pages. If you want collect all the info, you need the Bound Object. I use it with json serialization to manage more complex data.

If you are going to work a lot with the browser, I recomended you to inject your own scripts in the page in which you navigate. In this form, you can work with your own classes in the loaded page.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Victor