Problem/Motivation
Many browser tests only need to check something in the returned content of a controller. This can actually be done in a Kernel test if the functionality provided by a new drupalGet() method is utilised.
Kernel tests are significantly faster than browser tests (see #3041700: [META] Convert some tests into Kernel or Unit tests).
Therefore by creating this new method it will allow many existing tests to be converted to kernel tests thus speeding up the pipeline as a whole.
Steps to reproduce
Use a drupalGet() method inside a Kernel test. It will not work because no KernelTestBase::drupalGet() method is currently defined.
Proposed resolution
Add a trait for kernel tests which provides a drupalGet() method. This new version of the existing drupalGet() method BrowserTestBase::drupalGet() will perform the same tasks the current version does but in Kernel tests. The method should output HTML files for debugging in the same way that BrowserTestBase::drupalGet() does.
Code added to KernelTestBase should avoid duplicating code in BrowserTestBase -- this may require code being refactored from UiHelperTrait to a new trait.
Also to consider (for naming of traits, refactoring choices etc) is that as a follow-up, submitForm() should be made available to KernelTestBase as well.
Remaining tasks
User interface changes
None
API changes
A new trait for kernel tests, HttpKernelUiHelperTrait, which allows kernel tests to make HTTP requests to the test site, and make assertions against the returned content with Mink.
Data model changes
None
Release notes snippet
A new trait for kernel tests, HttpKernelUiHelperTrait, allows kernel tests to make HTTP requests to the test site and make assertions against the returned content with Mink. This has the potential for many browser test to be converted to kernel tests, which are much faster to run because unlike browser tests, they don't set up a test site by running the Drupal installer through browser requests.
Notes from original report
Working-but-in-need-of-clean-up code is here:
- Add code from https://github.com/mglaman/drupal-test-helpers/tree/main/src to KernelTestBase
- https://github.com/mglaman/drupal-test-helpers/commit/2dbed403a1872a3aea...
- https://git.drupalcode.org/project/layout_paragraphs/-/merge_requests/12...
| Comment | File | Size | Author |
|---|
Issue fork drupal-3390193
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3390193-11-backport-add-drupalget-with-mink-to-kerneltestbase
changes, plain diff MR !15281
- 3390193-add-drupalget-with-mink-to-kerneltestbase
changes, plain diff MR !9826
- 3390193-kerneltestbase-add-drupalget
changes, plain diff MR !9043
- 3390193-add-drupalget-to-kerneltestbase
changes, plain diff MR !4911
Comments
Comment #2
joachim commentedComment #3
joachim commentedMade a very rough start on a branch.
Comment #4
catchComment #5
dwwThanks!
-Derek
Comment #6
joachim commentedGot it working!
The methods in BrowserHtmlDebugTrait aren't going to work for kernel tests -- they occasionally make assumptions about having Mink. So my approach has been to make a parallel KernelHtmlDebugTrait. It deviates very little, so as a follow-up we should look at merging them. Things to change include:
- make the $message param of htmlOutput() required, not optional. There's only one place that htmlOutput() is called without it
- changing how getHtmlOutputHeaders() works
- fixing how $this->htmlOutputTestId is obtained
- ... other things I may have forgotten about.
Comment #8
larowlanLinking the original issue
Rather than adding one method, writing a kernel based mink driver would be ideal
Comment #9
larowlanCopying from slack
https://github.com/symfony/symfony/blob/6.4/src/Symfony/Bundle/Framework...
Looks like symfony have a solution for this that uses sub processes
If we can get this to work we get drupalGet, submitForm, assertSession etc all for free
That was the original intent of step 3
Comment #10
catch#3454092: Convert WebAssertTest to a Unit test is doing a similar-ish thing - except converting a functional test to a unit test where only very specific markup/headers need to be tested.
Still think this is a great idea and there are probably dozens or hundreds of tests we could change from functional to kernel tests using this.
Comment #11
catchComment #13
samitk commentedRerolled with 11.x branch, only one phpunit https://git.drupalcode.org/issue/drupal-3390193/-/jobs/2285879 issue is remaining.
Thanks
Samit K.
Comment #18
pooja_sharma commentedI have created separate MR 9043, removed the unrelated changes, observed pipeline test failures.
Couple of things observed:
Drupal\KernelTests\Core\DrupalKernel\DrupalKernelTest
run successfully on local, it seems more of issue on here.Root cause of issue: Undefined global variable $base_url (core/tests/Drupal/Tests/KernelHtmlDebugTrait.php:130)
If I tried to add empty condition & set the default value conditionally test passed (pipeline can be observed)
I believe in below code of line, both of these variables , need to evaluate further whether these are set or not on giltlab as on local issue not replicateor either can provide suggestions that 'll be quite helpful to fix it further.getenv('BROWSERTEST_OUTPUT_BASE_URL') ?: $GLOBALS['base_url']; (core/tests/Drupal/Tests/KernelHtmlDebugTrait.php:130)
Comment #19
joachim commented@pooja_sharma What's the reason for the new MR?
Comment #20
pooja_sharma commentedAs the existing MR in draft state & issue is not replicating on local it seems issue occurs due to existing MR is behind some commits.
I don't have access to move the existing MR from draft state, already showing error message on existing MR: Merge request must not be draft, due to which can't even rebase from gitlab or also facing issue on local to rebase it.
if you noticed even I have started working initially with existing MR, however due to restriction or not have access of existing MR, I end up with raising new MR along with other changes mentioned in detail (prev comment.), if existing MR in open state then it would be easier for me as well to proceed forward rather than creating new MR..
Comment #21
joachim commentedUnless your work is a different approach to the existing MR, there's no need to create a new MR, it just adds confusion.
The MR can be added to even with it in draft state - I can change it when it's ready for review, but IIRC
Comment #23
pooja_sharma commentedGot your concerns, to avoid confusion I have set visibility hidden of new MR. & already addressed query (#13) for test failures in prev comment.
Comment #24
catchA good candidate for this change: #3469573: Speed up ElementTest.
Comment #25
smustgrave commented@catch so work should continue here correct?
Comment #26
samitk commentedHello,
Fixed the failing test cases.
Thanks
Samit K.
Comment #27
longwaveDiscussed this at Drupalcon Barcelona in @joachim's BOF.
In order to implement @larowlan's suggestion in #8 we researched what Symfony does and found that the framework bundle already provides a BrowserKit implementation that can talk to a Symfony Kernel: https://github.com/symfony/framework-bundle/blob/7.1/KernelBrowser.php
It is hopefully possible to copy this class into Drupal core, wrap it in a BrowserKitDriver, and instantiate a Mink session from KernelTestBase - and we should be able to do almost anything that BTB can do in terms of calling drupalGet() and WebAssert assertions from KTB.
Comment #28
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #29
joachim commentedWorking on this.
Comment #30
joachim commentedGot the basics of this working!
Getting @larowlan's suggestion to work was actually easier than we thought when discussed in the DrupalCon BoF -- all it needed was a 1-line method override! :D Symfony already has a Mink driver that operates the Symfony HTTP kernel.
I found a simple functional test, NodeLinksTest, to convert as a trial, and very satisfyingly it's down from:
> Time: 00:35.962, Memory: 12.00 MB
to:
> Time: 00:09.708, Memory: 12.00 MB
However...
HTML debug output isn't working yet. This is because KTB sets $this->siteDirectory to be in a VFS, and initBrowserOutputFile() doesn't know how to work that. (Also, KTB *really confusingly* sets $this->siteDirectory *twice!???). Not sure how to fix -- do we just chage initBrowserOutputFile() to handle that sort of path too?
KTB has drupalGet(), but it doesn't yet have the ability to submit forms. I'd suggest leaving that to a follow-up, unless it turns out to be fairly simple.
As ever, writing kernel test really confronts you with all the assumptions that code makes. Turns out node module has a hidden dependency on field module, for instance!
We might want to as a follow-up allow installConfig() to only install specific config items, as for example in NodeLinksTest, I'm having to install field module just so installConfig() doesn't complain about not having a schema for field.storage.node.body, which we don't care about here.
Tests that need to check for things like cache headers won't work with this, because AFAICT that's added later on after the HTTP kernel has returned a response.
Tests that work with block output don't currently work with this, because there's no current theme. I've tried enabling stark and placing blocks, and that's not working. I switched to working on converting a test that didn't use blocks rather than go down that rabbithole. That's maybe something that can be figured out though -- maybe in a follow-up, unless it's simple.
Comment #33
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #34
joachim commentedFigured out the blocks issue.
Comment #35
joachim commentedWhy doesn't the response from the HTTP kernel have cache headers? Are those added later in a normal request? And how -- is there a way we can get them here too for tests that need them?
I think we should add a helper to do this:
as it's pretty tedious boilerplate.
Comment #36
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #37
joachim commentedThat seems like a false negative to me -- I can't change the signatures of methods in a trait that other classes are using too.
Comment #38
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #40
mstrelan commentedFixed phpcs and updated the baseline in this MR, but also opened #3480442: Add return types to UserCreationTrait for a proper fix so we don't need to keep adding to the baseline.
Comment #41
joachim commentedThanks!!
I'm pondering whether to move the methods added to KernelTestBase to a trait, where I'd also add a helper method for setting the active theme.
Thoughts on doing this (and also on what to call the trait? I'm thinking KernelTestUiHelperTrait)
Comment #42
mstrelan commentedI'd be interested to see if we can use something like this in Drupal Test Traits, to perform a drupalGet without the browser. So for that it would be nice to have these in a trait, and not necessarily with the words "kernel test" in it. But having the theme stuff in that same trait would not be useful in that regard, but perhaps wouldn't hurt.
Comment #43
joachim commented> So for that it would be nice to have these in a trait, and not necessarily with the words "kernel test" in it.
We need something in the name to differentiate it from UiHelperTrait which is for browser tests. HttpKernelUiHelperTrait?
Comment #44
joachim commentedIt's been a week with no objections to either the plan or the name, so I've moved the code for making requests to a trait.
Comment #45
larowlanThis looking great, we can mark #2469717: Step 3: Create a KernelWebTestBase base class/driver as a duplicate
When we start doing conversions, we should work on adding base-classes and traits first. The amount of additional setup required for e.g. a node module test is going to be a fair bit, so having that in all the tests would mean a lot of duplication otherwise.
Needs a reroll at present
Comment #46
joachim commentedRebased.
Comment #48
mstrelan commentedI did some further tinkering with this. Wondering if we actually need our own
::drupalGetin the trait rather than just using the one fromUiHelperTrait? I tried it out and it worked, but I had to implement::prepareRequestas an empty function.I wonder if drupalGet, drupalLogin and drupalLogout should be moved out of UiHelperTrait, they have nothing to do with the UI, whereas the rest of that trait has methods like click, clickLink, etc which make a lot more sense. Then we would rename this trait something like HttpKernelHelperTrait (without the word UI) and it would use whatever trait has drupalGet. Same applies for the assertSession method, we don't need to duplicate it if we can share the existing one.
Not sure if this helps or just complicate things.
Comment #49
joachim commentedMy plan with this issue -- and I see it's not stated above, but I do remember discussing it with people, probably @longwave? -- was to add the functionality in this issue, and then in a follow-up do refactoring for shared code with browser tests.
I think I'd rather keep the two drupalGet()s separate as there are things in one that aren't relevant at all in the other -- for example, every request calling isTestUsingGuzzleClient() makes no sense in a kernel test.
However, if you think HttpKernelHelperTrait is a better name than HttpKernelUiHelperTrait, to match up with future refactoring, then let's change that. I'm not sure about 'Ui' in a trait about making requests either.
I'm not sure about the latest commit --
The browser test drupalGet() doesn't return anything, so why should the kernel test version?
Comment #50
mstrelan commentedOk that all makes sense about refactoring and keeping them separate.
It does return something:
See: https://git.drupalcode.org/project/drupal/-/blob/11.0.5/core/tests/Drupa...
Comment #51
joachim commentedOh. I was looking at the actual return type... :/
Ok in that case setting back to needs review.
Comment #52
joachim commentedSomething for a follow-on: I think I've worked out how to get page caching working with this.
I'm working on #3487785: Cacheability is not respected when not using a lazy builder and I can get the test to fail (as expected) if I do this:
- enable page_cache module
- kill CommandLineOrUnsafeMethod, which prevents the page cache from handling the request.
EDIT: no, would need further investigating.
The NoSessionOpen policy fails because SessionConfiguration reports there is no session. We could just remove that policy too, but that feels a bit wrong, as that's part of what we might be wanting to test.
Comment #53
joachim commentedRebased.
Comment #54
geek-merlinLooks like tests are red..
Comment #56
joachim commentedTests are green and @alexpott has made the changes from his review, so this is back to NR.
Comment #57
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #58
joachim commentedRebased.
Comment #59
dwwThis is starting to look great, thanks for working on it!
The latest pipeline has failures in NodeLinksTest now that it's converted to Kernel, among other test failures.
Also, initial look at the open threads on the MR and I think all of them are good suggestions that should be incorporated into this.
I'll try to do a more thorough review soon. This is really exciting!
Thanks again,
-Derek
Comment #60
joachim commented@dww The reason those methods don't have return types is to keep them aligned with the same methods in BrowserTestBase and UiHelperTrait with a view to refactoring them to common code in a follow-up.
Thanks for the review!
PS. Fixed the test.
Comment #61
nikolay shapovalov commentedLooks like a great idea, thanks.
I left my feedback on MR.
Comment #62
joachim commentedThe @return void lines were added in this:
commit c69f5917e5c3366e2e35db5d36a679356135c27f
Author: Alex Pott
Date: Fri Dec 13 13:12:45 2024 +0000
Add return types to minimise the baseline
looks like they're there for a reason.
Comment #63
nikolay shapovalov commentedI was wonder about theme used, but you already mentioned that there is no theme.
I don't clearly understand what does it mean?
You already mentioned limit - "block placing". Are there any other limits?
Comment #64
joachim commented> I was wonder about theme used, but you already mentioned that there is no theme.
> I don't clearly understand what does it mean?
I've reworded on the docs about that a bit. Does that help?
Comment #65
joachim commented> You already mentioned limit - "block placing". Are there any other limits?
I had a read back of this issue, and there's page caching too. I've added docs about that. Hopefully that can be figured out in a follow-up, in which case we'll just remove the docs :)
Comment #66
oily commentedComment #67
oily commentedEdited the issue summary. But there are several fields still blank. Also
I dont know what that means, if someone can please clarify. What can those links supply to this issue?
Comment #68
nikolay shapovalov commentedThanks for update MR. New wording looks good, and thanks for provide information about caching.
I check MR again and have one more question.
I love this feature, and I am going to use it as soon it will be deployed. Thanks for your effort.
Comment #69
joachim commentedCleaned up IS.
Comment #70
oily commentedWe are going to need a CR for this, i think.
Comment #71
oily commentedComment #72
joachim commentedAdded a CR.
Comment #73
oily commentedThank you @joachim. Great work on this issue!
Comment #74
oily commentedEdited the CR, reworded in places: to emphasise the new possibilities the change provides!
Comment #75
dwwI also did some minor edits (mostly just formatting) on the CR, and agree that looks really good.
Tried to resolve as many of the MR threads as I could.
There's still some valid feedback that hasn't been addressed.
Re: #60: Any future refactoring to consolidate these traits and methods will end up being more strictly typed. As new code, I believe our policy is to add always strict types, including return values. No code will be simultaneously using both traits -- a test is either Functional or Kernel but not both. I don't understand any benefit of leaving off the return types. There's no BC to preserve, since this is all brand new.
Thanks!
-Derek
p.s. Initial pass at saving credit:
@joachim, @pooja_sharma, @samit.310, @mstrelan, @alexpott, @mxr576 for code.
@oily, @nikolay shapovalov and myself for reviews.
Comment #76
joachim commentedResolved everything and rebased.
Comment #77
catchI still need to review this properly, but overall it looks good.
Tagging for an 11.2.0 priority - doesn't need to be tied to a release as such because 95% of the usage will be in core tests, but if we get this in, we can start to work on conversions, and it'll be available to contrib earlier etc.
Comment #78
catchComment #79
larowlanThere's some comments in the MR from @mstrelan that need addressing - thanks for working on this
Comment #80
joachim commentedResolved 2 comments. I think the 3rd can be left as is.
Comment #81
mstrelan commentedI tried to convert a few BrowserTestBase tests to kernel tests to try this out and have a couple observations.
1. \Drupal\Tests\help\Functional\HelpPageOrderTest
This calls
$this->getTextContent()which in BrowserTestBase resolves to\Drupal\Tests\UiHelperTrait::getTextContent, but in KernelTestBase resolves to\Drupal\KernelTests\AssertContentTrait::getTextContentwhich depends onAssertContentTrait::setRawContentbeing called. Of course the test could be refactored to not call$this->getTextContent(), but I'm wondering if we should do something about that here, such as callingAssertContentTrait::setRawContentindrupalGet. Once I worked around it the Kernel test passed in 3 seconds as opposed to 12 seconds before.2. \Drupal\Tests\block_content\Functional\BlockContentPageViewTest
This required lots of refactoring, which is as expected, but I couldn't get the
foobar_gorillablock to load in::testPageEdit. I was able to manually place thesystem_powered_by_blockand get that to load, so I'm willing to concede that there's probably something I'm missing, rather than blocks not appearing because of the new drupalGet.3. \Drupal\Tests\dynamic_page_cache\Functional\DynamicPageCacheIntegrationTest
This was probably a bit ambitious. The first blocker I couldn't easily work around was that the default cache policy for DPC prevents caching if PHP_SAPI is cli, which it is now that we're in a Kernel test. This might lead to other gotchas down the track if folks are expecting DPC to work when using drupalGet in Kernel tests.
That's as much as I've got time for today. Unfortunately I didn't find an existing test that could be converted cleanly, but nonetheless I'm quite excited about this.
Comment #82
joachim commentedThanks for having a look!
AssertContentTrait wasn't change by this MR.
I'm not sure whether to:
a. call AssertContentTrait::setRawContent in drupalGet()
b. change getTextContent() to use Mink, since that's now available? And then we can later on deprecate setRawContent().
> but I couldn't get the foobar_gorilla block to load in ::testPageEdit
Could you push your work on that test somewhere so I can have a look at it?
> The first blocker I couldn't easily work around was that the default cache policy for DPC prevents caching if PHP_SAPI is cli
The docs on HttpKernelUiHelperTrait::drupalGet() say that page caching modules won't work. Should that list of warnings be moved from the method docs to the trait docs, so they're more visible?
Comment #83
joachim commented@mstrelan could you try adding this to the top of AssertContentTrait::getTextContent()?
Comment #84
mstrelan commentedI'm not sure either. I don't think we can deprecate
setRawContentbecause I think some tests use it after directly rendering things not usingdrupalGet. Note thatUiHelperTrait::drupalGethas this comment that I think is not true either, becausesetRawContentonly happens in KernelTestBase.@mstrelan could you try adding this to the top of AssertContentTrait::getTextContent()?
Yeah that worked. I think probably the simplest solution is to call
$this->content = $out;from the new drupalGet, that also makes it work.Added patches for POC I did on block_content and help tests.
I must admit I didn't look at the docs, I just expected it would work like BrowserTestBase. I don't think moving them would make a difference. I do think we could probably work around this though by either replacing that policy or modifying it to use a service that can be replaced or decorated to pretend it's not the CLI. But this is best suited to a follow up.
Comment #85
mstrelan commentedOpened #3517299: Convert functional tests in help module to kernel tests with a few more help tests I converted, see https://git.drupalcode.org/issue/drupal-3517299/-/merge_requests/1/diffs for the diff.
Comment #86
mstrelan commentedAdded a comment in #3517299-4: Convert functional tests in help module to kernel tests that the only thing blocking multiple requests in the tests I've looked at is the missing preceding slash.
UiHelperTrait has a buildUrl method that ensures the path is absolute, I think we need to do the same thing here.
Comment #87
mstrelan commentedSome more conversions at #3519393: Convert functional tests in navigation module to kernel tests.
I was able to work around more issues with multiple
drupalGetcalls, this time with lazy builders. The issue was that we needed to reset theMemoryBackendcache after the firstdrupalGet. This is something we already do inUiHelperTestby calling$this->refreshVariables. Here is the commit in that issue in case we want to add it here.But I ran in to another issue - installing modules doesn't reset the hook implementations. So in
NavigationDefaultBlockDefinitionTest::testNavigationDefaultBeforeNavigationwhere it tries to installnavigation_test_blockfirst, andnavigationlater, the hook_page_top implementation in navigation module is never invoked. I'm not sure a way to solve that yet.Comment #88
larowlanOne thing to make sure we have here is support for sessions.
There are some core access checks (most notably the CSRF one) that don't run if there's no session, which would mean the result we get here is different to that with BrowserTestBase
Comment #90
joachim commented> One thing to make sure we have here is support for sessions.
Does anyone have any ideas about how to make sessions work?
Comment #91
geek-merlin@joachim As of sessions...
I think it's Drupal sessions, not mink sessions.
Which is about cookies.
One year ago (time flies!) you made a commit on this:
> 889f5a0a - Removed sessions and cookie stuff from initMink(). We don’t need this?
HTH!
Comment #93
joachim commented@geek-merlin the stuff that commit removes doesn't do anything -- BrowserTestBase::registerSessions() is empty.
I'm not sure how we get this to work -- we have several layers of stuff, so where the cookies happen is ???
Comment #94
joachim commentedLooks like some work I did on #3487785: Cacheability is not respected when not using a lazy builder is relevant here.
Comment #95
joachim commented> But I ran in to another issue - installing modules doesn't reset the hook implementations.
Are there any existing Kernel tests which install modules within the test? How do they reset hook implementations? There is this in ConfigOtherModuleTest but I don't understand what it's doing:
> One thing to make sure we have here is support for sessions.
I've added a test -- which doesn't work, but it'll help with figuring this out.
Is sessions support not something we could leave for a second phase?
Comment #96
catchI think session support could definitely be left for a follow-up, there are dozens/hundreds of tests we could shift without needing to be logged in.
Comment #97
joachim commentedAha! The session problem is because our Session middleware checks for PHP_SAPI == 'cli' -- same problem as the cache policies!
If I hack the PHP_SAPI condition to make it fail then the session test works :)
Comment #98
godotislateRe #95: After a module install, the Drupal container is rebuilt and is actually a completely new object, so the test class property needs to be updated to reference the new container object instead of the old one, which is what
ConfigOtherModuleTest::installModules()does.#2066993: Use magic methods to sync container property to \Drupal::getContainer in functional tests introduced a magic getter to sync the
containerproperty to\Drupal::getContainer()in browser tests for this reason. I don't recall what, if any, discussion about kernel tests there was, but it's a long thread.Comment #99
joachim commented@godotislate so if we add a copy of ConfigOtherModuleTest::installModules() to our trait, or create a new trait for module management, we'll have something that works?
Comment #100
godotislateI think this is too general a statement for me to make, especially since I haven't looked at the use case here,
I'm only speaking to after you do any container rebuild in a kernel test, you have to be careful that the test class's
containerproperty, any other properties and local variables that are services or derived from services, such as entity storage handlers, need to be refreshed to reference the new container object and services from the new container.Comment #101
joachim commentedOk, thanks!
At any rate, making installation of modules during the test work is definitely a follow-on, as it's separate from this issue -- it's something that already needs special handling in Kernel tests, and the work here doesn't change it at all.
MR is ready for review --
- sessions work (with test coverage!)
- page caching works (if you set up your test class for it -- instructions in the docs for the trait)
Comment #102
joachim commentedPS the thing about Symfony's HTTPKernel browser handling paths that don't have an initial is maddening -- I had tests that were failing because of it, and yet NodeLinksTest is working fine with making two requests to 'node'. No idea. AAARGH.
PPS. No wait, I get it -- it's because 'node' only has one path component, so it works. A request to 'foo/bar' followed by a request to 'biz' will take you to 'foo/biz' because it treats 'biz' (as opposed to '/biz') as relative, so it strips the 'bar' and puts 'biz' instead. Of course, the code that does this is TOTALLY undocumented.
Comment #103
joachim commentedBTW Re #87 I'm not sure what was happening with your hooks, as when I put this in a kernel test, it shows the new cron hook ran:
Comment #104
mstrelan commentedI suspect a lot has changed with hooks and module installs since #87, I remember seeing some issues that I thought might have helped here.
Comment #105
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #106
joachim commentedBTW regarding form submissions, which should be a follow-up to this issue -- I had a quick play with copying submitForm() from UiHelperTrait, and that is going to be blocked on #2505339: Stop using getMainRequest() to build $form['#action'].
EDIT: I've made the follow-up: #3566881: Add a submitForm() method to HttpKernelUiHelperTrait.
Comment #107
joachim commentedFixed the problems the bot found.
Comment #108
znerol commentedI'm impressed with the performance improvements the
HttpKernelBrowserbased mink driver brings. Great work!At the same time I'm surprised that sessions seem to work fine with the changes proposed here. My expectation was that making session work with the kernel browser would require replacement of various session components.
The MR removes the cli SAPI check from the session middleware. But there are still various cli SAPI checks in place in
SessionManager. Those checks, e.g., prevent that session data is stored and loaded from the database.I stepped through
KernelTestHttpRequestTest::testSession()with a debugger in order to realize what happens here.In functional tests, the response is generated in the test child site. The container and all its services are newly initialized in every request. The session subsystem relies on cookies in order to correlate session identifiers with session data in the database. This is exactly how request/response cycles work with HTTP user agents.
In kernel tests, the container instance is constructed once and is reused throughout the test. The response is generated in the test runner. This means that the same session instance is reused across requests. The MR as it currently works doesn't respect cookies on the request object and doesn't store/retrieve session data into/from the database.
One could argue that the current MR relies on state leaking from one request to the other in order to support sessions. I'm unsure whether this is the desired behavior.
I can imagine one situation where this breaks: Namely if something triggers a mid-test container rebuild. That could be acceptable if the goal of this issue is to speed up testing of simple cases.
Other things like
SessionManager::regenerate()(used at login/logout) will be more or less no-ops. I'm honestly not sure about the consequences of that.Comment #109
catchI think that it probably is but we should try to define what simple is.
We have a lot of functional tests that set up a few things, hit a single page, then do some assertions. Those will be great candidates to convert to this.
There are probably also some tests, especially around access stuff, where we want an authenticated user to trigger certain cache contexts etc., but that again is probably a single request scenario so could come under the simple case, and the details of session storage etc. don't matter we just need a session at all.
Once we get into multiple requests with an authenticated user, then either that indicates a test that's doing setup in the UI (very common pattern for tests added 15 years ago) where instead it only needs the request to the 'final' page, or something that should be left as a functional test because it really wants to test as close to a real site. There are bound to be tests in-between these but we don't need to start with those.
Comment #110
ghost of drupal pastThis is the first time I see going with the Symfony kernel actually benefits Drupal. Took long enough but wow. This is amazing. Thanks for the hard work.
Comment #111
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #112
joachim commentedComment #113
joachim commentedDiscussed this some more with @ghost of drupal past on slack, and I think that instead of having sessions which *mostly* work but will fail if you do something like rebuild the container, it's clearer and simpler to not support sessions at all for now.
Making sessions work can be done in a follow-up.
The main problem might be at the PHP level -- on the CLI, calling setcookies() doesn't result in anything going into PHP's headers. But then, I can't find ANY code that calls that, and there's a lot of spaghetti code in Symfony's HTTP and sessions stuff.
In the meantime, I've removed the sessions handling from the MR.
Comment #114
znerol commentedCookie management of native PHP session is done in the engine (session.c). Looking at Symfony is not enough to comprehend whats happening under the hood.
Comment #115
catchNo session management for a first step seems like a good idea, we can open a follow-up.
We have at least some tests that give editor/admin permissions to anonymous users to save creating an authenticated user, and that's also a possible workaround for some simpler tests of functionality that's behind permissions.
Comment #116
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #117
joachim commentedMR 4911 is obsolete.
Comment #119
catchThis looks really encouraging to me. The amount of new test framework code is minimal, most of the MR diff is actual test changes.
One question - is node links test really added or should that be getting deleted from a functional test somewhere?
Comment #120
joachim commented> One question - is node links test really added or should that be getting deleted from a functional test somewhere?
Ah yes, I'd originally left the functional test in for comparison, but I've now removed it.
Comment #121
catchOne more question - do you know where the phpstan baseline additions are coming from? If it's using different traits that makes sense, but I would have thought things would move around rather than being added as such.
Comment #122
joachim commentedThey are from new kernel test classes, and the methods it's complaining about are from traits. It is noisy -- can we tell it to complain about the traits instead of where the traits are used?
EDIT: no, we can't -- https://phpstan.org/blog/how-phpstan-analyses-traits (thanks @godotislate on slack)
Comment #123
godotislateIs it possible to add a
voidreturn type toHttpKernelUiHelperTrait::alter()? It would remove those new entries in the baseline.Comment #124
joachim commentedHttpKernelUiHelperTrait::alter() is implementing an interface method.
Comment #125
godotislateRight, but you can add a return type to an implementing class method even if the interface doesn't have one.
The only issue is whether an existing subclass of that class also implements alter and does not have the void return type. But that seems really unlikely?
Comment #126
joachim commented> The only issue is whether an existing subclass of that class also implements alter and does not have the void return type. But that seems really unlikely?
I'm pretty sure we count test classes that aren't base classes as internal, so it's fine.
I've done alter().
But htmlOutput() and initBrowserOutputFile() come from BrowserHtmlDebugTrait, which isn't getting changed in this MR.
Comment #127
godotislateYup, looks fine. I haven't looked in depth at the rest of the MR, so not sure how close this is otherwise. But that change specifically looks fine to me.
Comment #128
joachim commentedI'm stuck on how to fix this for PHPStan:
> {"type"=>"ERROR", "message"=>"Call to an undefined method Drupal\\KernelTests\\KernelTestBase::getSession()."}
Comment #129
catchMR is green again, I made three changes:
1. Explictly used one of the traits in KernelTestBase so that phpstan would notice - it doesn't like traits imported via other traits apparently.
2. Added a couple of phpstan baseline entries for existing traits.
3. Had to rename an ::alter() method in the hook ordering kernel test, because it conflicts with the new ::alter() method on KernelTestBase. Only one example in core but maybe we should note this in the CR?
None of these are substantial changes so I'm going to go ahead and RTBC here.
Comment #130
joachim commentedI'm going to write some docs for https://www.drupal.org/docs/develop/automated-testing/phpunit-in-drupal/...
Comment #131
joachim commentedArgh, didn't mean to change status!
Comment #132
joachim commentedDocumentation page text:
------
Kernel tests can make HTTP requests and make assertions against the returned
content, similarly to Browser tests. The KernelTestBase class has methods
drupalGet() and assertSession() which work the same way as drupalGet() for
Browser tests. The Mink driver passes requests to Drupal's HTTP kernel, rather
than simulating a browser.
Kernel tests only support GET requests, not POST. This means that forms cannot be submitted.
Session semantics differ from normal page requests. Do not rely on session state beyond its existence (no persistence or regeneration).
The following aspects of a Drupal site need additional set up.
Current user
There is no logged in user. Use \Drupal\Tests\user\Traits\UserCreationTrait to set a current user:
Theme and blocks
There is no active theme. To place blocks, a test must first install a theme and then set it as active:
Page caching
Page caching modules must be set up to function. See \Drupal\KernelTests\KernelTestHttpCacheRequestTest and \Drupal\KernelTests\KernelTestHttpDynamicCacheRequestTest for examples.
Comment #133
longwaveAdded some feedback on the MR, and also this note on the CR:
This is not what the change record says:
Why do we need the leading slash adding?
Comment #134
joachim commented> Why do we need the leading slash adding?
I can't remember, but there are reasons. Something to do with the HTTP kernel expecting that maybe?
It's really browse tests that are in the wrong here, since Drupal paths always have to start with a / now.
@longwave I'll tweak the docs. I think that checking for an initial slash is going to be faffy.
Comment #135
catchUiHelperTrait::drupalGet() has explicit handling for forward slashes via ::buildUrl()
I can't remember the details of why, probably dates back to simpletest, but functional tests are extremely forgiving due to this - partly to support drupalGet() of completely unrouted or even external URLs.
I think it's fine for kernel tests that only support routed paths to be stricter, so updating the docs/CR seems fine.
edit: talked to longwave and joachim in slack seconds after posting this and changed my mind to the other direction, probably easier to make it work both ways.
Comment #136
joachim commentedChanging drupalGet() to NOT want an initial /, for consistency.
Moved the page cache stuff to a trait.
Comment #137
catchI added void return types to the two methods in the existing trait, I agree it's highly unlikely anyone will be overriding them, much less returning anything from them. This makes phpstan happy. Moving back to RTBC since that's a tiny change.
Comment #138
longwaveDid a final review of this, no further notes. Looking forward to converting the slower tests and using this elsewhere.
Committed and pushed to main, thanks!
Did not backport cleanly but we should land this in 11.4 as well, marking for backport. Did not publish the change record yet, let's do that when it lands in 11.x.
Comment #141
joachim commentedCherry-picked to a new branch 3390193-11-backport-add-drupalget-with-mink-to-kerneltestbase
Comment #142
catchOpened #3582242: Convert functional tests with suitably simple HTTP requests to kernel tests for next steps!
Comment #143
godotislateKernel test failure:
https://git.drupalcode.org/issue/drupal-3390193/-/pipelines/782982/test_...
Comment #144
joachim commentedLocally I get this, but I get it on the 11.x branch too!
Comment #145
joachim commentedComment #146
catchis missing 'node' in the namespace declaration.
Comment #147
catchComment #148
catchTest failures here were due to #3575605: Move tests of search to search module which somewhat bizarrely only appear to show up on 11.x despite the same issues being in both 11.x and main.
Back to green here again.
Comment #150
longwaveCommitted and pushed to 11.x, thanks!
Also published the change record.
Comment #154
joachim commentedCould a maintainer add credit to @mglaman in the contribution record please? His work on https://github.com/mglaman/drupal-test-helpers/blob/main/src/RequestTrai... was the inspiration for this.
Comment #155
longwaveSure, done.
Comment #156
joachim commentedCould this also be backported to 10.x?
Comment #157
dww+1 to backport, FWIW. I'd love to be able to do this in more of my contrib projects, too, but it'd be a PITA to only be able to change the tests for D11. Basically everything I maintain still supports D10, too.
Comment #158
godotislateThe release managers are considering a backport, but since there won't be 10.7.x, this would have to go in a 10.6.x patch release, in which case it'd probably also need to be backported to 11.3.x for a patch release there as well.
Also, would backporting this issue alone be sufficient, without any of the follow ups?
Comment #159
dwwThanks for considering it. If we backported, I think we'd want some of the follow-ups, too. At least to flesh out the API surface of the trait. In particular:
#3582769: Add clickLink() to HttpKernelUiHelperTrait
And assuming they land in the near future:
#3582228: add $options and $headers parameters to HttpKernelUiHelperTrait::drupalGet()
#3582985: Add content from HttpKernelUiHelperTrait::drupalGet() to $this->content for use by AssertContentTrait
I'm less interested in backporting the conversions, but I think backporting the "full" trait would be great (if we can).
Thanks again!
-Derek
Comment #160
godotislateDiscussed with @longwave and @catch, and what we're leaning toward is backporting to 11.3/10.6 all the relevant issues together, once they're complete. Per #159, that's currently:
#3390193: Add a drupalGet() method to KernelTestBase (this issue)
#3582769: Add clickLink() to HttpKernelUiHelperTrait
#3582228: add $options and $headers parameters to HttpKernelUiHelperTrait::drupalGet()
#3582985: Add content from HttpKernelUiHelperTrait::drupalGet() to $this->content for use by AssertContentTrait
We can reassess if it turns out other issues need to be included.
Moving this and #3582769: Add clickLink() to HttpKernelUiHelperTrait to "Patch to be ported" so that they'll be easier to find later.
Comment #161
dwwCool, sounds great. Thanks!
Would it be easier to merge more of the follow-ups into a single issue / MR?
Comment #162
catch@dww that would make the backport easier, not necessarily the initial commits. But we could do an omnibus backport MR somewhere definitely so we're sure all the cherry picks are correct etc.
Comment #163
smustgrave commentedShould this be backported before #3582228: add $options and $headers parameters to HttpKernelUiHelperTrait::drupalGet()?
Comment #164
godotislateSee #160. 4 issues are going in together, probably with a combined MR. #3582985: Add content from HttpKernelUiHelperTrait::drupalGet() to $this->content for use by AssertContentTrait is still outstanding.
Comment #165
smustgrave commentedCan we close #3582228: add $options and $headers parameters to HttpKernelUiHelperTrait::drupalGet() then? It's committed but also marked backport.
Comment #166
godotislateI'm not sure I understand? We're leaving them patch to be ported for 11.3.x/10.6.x.
Comment #167
smustgrave commentedSorry for the delay I meant if we are backporting
#3390193: Add a drupalGet() method to KernelTestBase (this issue)
#3582769: Add clickLink() to HttpKernelUiHelperTrait
#3582228: add $options and $headers parameters to HttpKernelUiHelperTrait::drupalGet()
#3582985: Add content from HttpKernelUiHelperTrait::drupalGet() to $this->content if it exists
As 1 MR can we close the ones that are committed and just leave this one open?
Comment #168
godotislateI'm not completely sure we're doing 1 MR, though @catch mentioned the idea and it probably sounds good. I think we can leave all the issues open to keep track of them, and they won't really be in 10.6 until we commit them. Since they'll be in the next patch release, I think we should keep the individual issue statuses aligned.
@dww or @joachim: Were there any others issues that should go in for this effort outside the 4 previously mentioned (including this one)?
Comment #169
joachim commentedKeeping track of other issues to backport in bulk:
- this one
- #3582769: Add clickLink() to HttpKernelUiHelperTrait
- #3589626: clickLink in kernel tests erroneously prefixes some paths with /
- #3582228: add $options and $headers parameters to HttpKernelUiHelperTrait::drupalGet()
- #3582985: Add content from HttpKernelUiHelperTrait::drupalGet() to $this->content for use by AssertContentTrait
Comment #170
godotislateIs #3589626: clickLink in kernel tests erroneously prefixes some paths with / needed for backporting the correct functionality to 10.6? Are there any other bug issues that need considering?
Comment #171
joachim commented