-
-
Notifications
You must be signed in to change notification settings - Fork 108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bunit.web.e2e: Make it easy to create E2E tests that execute JavaScript #144
Comments
Selenium will be slow. Could you use the Blazor HTML render to create your HTML string, set the HTML in a browser component, and have some kind of interceptor for IJSRuntime so you can assert exchanges between the two? I'm hoping that pumping HTML directly into the component will be quicker than automating a browser. Having a separate context for E2E could be good. It could be used to build up the rest of the page by specifying the |
My primary reason for thinking about Selenium is because that is what the Blazor team uses in their E2E tests, e..g. https://github.com/dotnet/aspnetcore/blob/master/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs An initial proof-of-concept need not be fast, it for the few cases that are hard to test in regular bUnit, and it is more important that it is easy to write stable and consistent tests. |
Selenium is what the Blazor team uses, but I expect they aren't happy that a single test takes a few seconds - which is one of the reasons bUnit is so good. I think my suggested approach would be stable and consistent, if it is possible at all. |
That would be great. Can you go into a bit more detail on what you mean by "a browser component" and "kind of interceptor for IJSRuntime". Remember, the need here is to actually run the JavaScript in a browser context, such that it can update the DOM if needed. Intercepting IJSRuntime invocations is already possible with bUnit, the problem is when JavaScript needs to execute to make a test work. The inspiration for this is #141. |
Something that will work on Windows, Mac, Linux: Like the Steve Sanderson used here - https://github.com/SteveSandersonMS/WebWindow
Your class implementation could act like a Mock but that actually passes the invokes through to the real IJSRuntime. In the Browser perhaps you could swap out the JS reference that calls through to C# and have it call your object instead (which then passes it through). A full man-in-the-middle scenario. |
I would love to see a proof of concept of this... admittedly I am having a hard time following your thinking of this. |
Would something like Playwright work? It seems to avoid some of the heartburn that Selenium has but still seems to drive a "full" (albeit, defaulted to headless which should help with the reliability,) browser. I have zero experience with playwright other than reading about it, so I could be way off base. Otherwise, I'd be happy to help work on a Selenium-based example. |
@PandaMagnus thanks. I think, in the spirit of Blazor, we need to stay as much as possible in C# land, which rules out playwright, as far as I can see, at least according to their page: "Playwright is a Node library to automate Chromium, Firefox and WebKit with a single API. Playwright is built to enable cross-browser web automation that is ever-green, capable, reliable and fast."
Please do. Create a Remember, to me, the point of this is not just to expose Selenium and its API to the user, but to wrap it in a bUnit like API. Otherwise, I would rather just post a guide in the docs on how to do e2e testing using Selenium, and not have to maintain an additional package. Ideally, we abstract away Selenium enough that it can be replaced with an alternative down the line. If too much of Selenium shines through, I think we should consider naming the package and project Before you commit a bunch of time to this, please post your design thoughts first so we can align and not waste time. Thanks! |
@egil Just following up on this. Apologies I got busy and this fell off my radar. It's back on it now, though, and I intend to sit down this weekend or next to put together a working proof of concept. |
No apologies needed. I look forward to see what you come up with. |
I gotta fix some issues with .net5 on my machine, so I'm not ready to PR this yet (should have that sorted out in a few days,) but if you wanted to look at the stubbed out version, it's here: I'm using a package I maintain for lightly wrapping Selenium so I don't have to re-implement a bunch of webdriver creation/webdriverwait code. If you don't want that dependency, it should be pretty straightforward to strip out and replace. I haven't added samples yet, but for comparison to the ClickMeTest, the E2E syntax will look something like:
|
I like it, but it's a bit verbose. What about having polling and timeout as defaulted parameters on Page.Find? Also consider making that async to improve performance on parallel tests (if that's even possible). |
Makes sense to default the timeout and polling. I'll make that change before I sort out my local copy and put the actual PR in. RE: async, Selenium doesn't have any async calls that I know of (forewarning, I haven't looked into the new version they've got in beta right now, so that could change,) but you can run tests in parallel as long as each test gets its own WebDriver instance. So as long as the test context is instantiated per test, parallelization shouldn't be a problem as long as so many don't run in parallel that the machine runs out of memory (each instance will get it's own chrome process.) |
I was thinking of the polling. If the 100ms loop were a Task.Delay Maybe a future enhancement after testing if it makes enough of a difference :) |
@PandaMagnus I am exited about this. A few thoughts based on what you have shared:
I have no experience with Selenium myself, so I do not know what is possible, so I am very happy to see lots of community engagement on this issue. Thanks again for your efforts with this! |
@mrpmorris Understood. I'll go back through Selenium's polling code, I seem to recall it's invoking Thread.Sleep() under the covers, but could be wrong. Thanks for catching that! @egil I'll do the PageComponent rename and defaulting today or tomorrow, along with moving implementation directly to Selenium. At that point, I'll put in the PR (I got my VS sorted out so I can actually verify the code builds now. :)) Do you want samples included with that? Point 3 is interesting; I'll tinker around with that as my next item to work with. |
With regards to testing when a component is rendered, we can also utilize the new In theory, we could create an adapter around components that are rendered in tests, so we know exactly when their life cycle method are called. At the very least, we will know when to poll the DOM, so it doesn't have to be after X ms, but can happen as soon as a component is rendered. That should help with the speed of the tests. |
@egil Okay, updated the syntax to mirror bunit closer. I also stubbed out an example of mocking Selenium to unit test this. Also pulled the project in to stub out a sample (although the sample will need a running website to truly work.) I'd like to take the time to add some real unit tests before putting the PR in. But let me know any other thoughts on this. I'm deliberately holding off on hooking into the IComponentActivator and/or using bunit output purely because I'm not as comfortable with that yet. #mrpmorris I did confirm that under the covers Selenium is using a Thread.Sleep in that particular polling statement. Although now that I type that, I suppose we could implement our own awaitable polling statement. I started to go down that route a year or two ago before abandoning it in favor of their current WebDriverWait (that uses Thread.Sleep.) The logic ended up looking a heck of a lot like Polly, which is why I ultimately abandoned it. |
Maybe I'm slow on the uptick, but what if a single razor component was dynamically wrapped into a single page for a test? For some reason I had it in my head that these tests would be used on a fully deployed website, but if we had a... I dunno, "page scaffolder" or something to consume a single razor component, deploy just that, and then run these tests, it would help maintain syntactic parity between the unit and E2E tests. |
You are heading in the right direction here. Bunit actually also uses a wrapper component that it renders the component under test inside. Have you looked at how the Blazor teams e2e tests works? I'm am pretty sure they are using Selenium, but have something like you are talking about here. |
@egil I had glanced through them a bit, but didn't spend much a whole lot of time going through them. I'll take a deeper dive into their e2e tests today. |
Yes, and they are slow |
Some test scenarios that rely on JSInterop and JavaScript to execute to work, cannot be tested with bUnit currently.
This could be solved by:
E2ETestContext
, in its sub library e.g.bunit.web.e2e
.E2ETestContext
to drive tests, and abstract away all the hardship of setting that up.E2ETestContext
and other APIs should mimic those in thebunit.web
library as much as possible, to make the transition between the normal component tests and e2e tests easier.Questions:
This could be based on the code that the Blazor team has in its E2E tests for Blazor.
The text was updated successfully, but these errors were encountered: