Problem/Motivation
I have a test for a custom module that does
$assert->pageTextNotContains('SBS');
This fails randomly if the theme token generated for drupal settings contains the string SBS. Unfortunately, \Behat\Mink\Element\Element::getText will return the text included inside the script tags in the body. The problematic one is:
<script type="application/json" data-drupal-selector="drupal-settings-json">{"path":{"baseUrl":"\/","scriptPath":null,"pathPrefix":"en\/","currentPath":"admin\/telus","currentPathIsAdmin":true,"isFront":false,"currentLanguage":"en"},"pluralDelimiter":"\u0003","suppressDeprecationErrors":true,"ajaxPageState":{"libraries":"admin_toolbar\/toolbar.tree,admin_toolbar_search\/search,admin_toolbar_tools\/toolbar.icon,classy\/base,classy\/messages,core\/drupal.active-link,core\/html5shiv,core\/normalize,seven\/global-styling,telus_toolbar\/toolbar,toolbar\/toolbar,toolbar\/toolbar.escapeAdmin,toolbar_menu\/icons,user\/drupal.user.icons","theme":"seven","theme_token":"qmmJrXPeEdpihFizSBSnD-kZanHo-4WzDf3L2F63puk"},"ajaxTrustedUrl":[],"toolbar":{"breakpoints":{"toolbar.narrow":"only screen and (min-width: 16.5em)","toolbar.standard":"only screen and (min-width: 38.125em)","toolbar.wide":"only screen and (min-width: 61em)"},"subtreesHash":"6pedNI4W-Gv5uhLYRwkHwC5MGZxlWKDbctxk3jJqFuM"},"adminToolbarSearch":{"loadExtraLinks":true},"user":{"uid":"2","permissionsHash":"d8e278451f423cb7d81f32499f7dc040316897215570611b7c41419636a2f629"}}</script>
as it contains a number of cryptographic strings that can contain any set of characters.
Steps to reproduce
Proposed resolution
I think this requires an upstream fix. I'll check Behat / Symfony's crawler for issues. The problem is that eventually this is getting the \DOMNode::$nodeValue for the page - see we're talking about core PHP functionality - see \Symfony\Component\DomCrawler\Crawler::text()
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-3175718
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:
Comments
Comment #2
alexpottComment #3
alexpottI think if we raise this upstream they're going to point out that doing
document.body.textContentin the browser console will return include the drupal settings json too.Comment #4
alexpottUpstream there's https://github.com/symfony/symfony/issues/8173
Comment #5
alexpottThis almost certainly causes core random fails. In
\Drupal\Tests\media_library\FunctionalJavascript\MediaOverviewTest::testAdministrationPage()we do$assert_session->pageTextNotContains('Dog');. Bumping priority as these types of random fails confuse and waste contributors time.Comment #6
longwaveMaybe we just need to discourage
pageTextContains()and be more specific withelementTextContains()?Comment #7
alexpott@longwave yeah that'd help but when thinking about contrib / custom - the mind boggles. Also we have over 1000 pageTextContains/pageTextNotContains in core.
Comment #8
jonathanshawI was wondering if anyone had raised a mink issue for this, but look what I found: #2784427: Browser tests: Plain page text contains CSS styles and JS settings, complete with patch from @klausi.
Comment #9
longwaveNice find, @alexpott even commented on that :)
https://www.drupal.org/files/issues/plain-page-2784427-28.patch looks close to what we are asking for here, should we just revive that issue?
Comment #10
longwaveComment #11
alexpottSo this is almost certainly an upstream bug in https://github.com/minkphp/MinkBrowserKitDriver. Here's why:
I'm going to open an issue and see what happens.
Comment #12
alexpottOpened https://github.com/minkphp/MinkBrowserKitDriver/issues/153
Comment #13
alexpottFWIW the fact that
\Behat\Mink\Driver\Selenium2Driverworks as expected means that #5 is incorrect.Comment #14
alexpottWe have
\Drupal\Tests\layout_builder\Functional\LayoutBuilderTest::testLayoutBuilderUi() - the second of those is unlikely to ever fail.
Comment #15
alexpottHere's proof of our problem. \Drupal\Tests\node\Functional\NodeTypeTest::testNodeTypeEditing will fail.
Comment #18
jungle>Maybe we just need to discourage pageTextContains() and be more specific with elementTextContains()?
How about a shorthand wrapping up elementTextContains() to check against the
bodyelement explicitly, and replace pageTextContains(), pageTextNotContains() usages in core further?For example: naming them pageBodyTextContains() and pageBodyTextNotContains()
Comment #19
jungleSelf-review: should be `does not contain`, not `does not contains`.
Comment #20
jungleComment #21
jungleOr assuming calling pageTextContains() is to check against the text of body. Then inside pageTextContains(), calls elementTextContains()
Comment #25
mondrake@jungle the drupal-settings-json is part of a
<script>tag that is contained in the<body>, so #21 is insufficient.I feel we need to revive the approach to override the Session and its getPage method from #2784427: Browser tests: Plain page text contains CSS styles and JS settings, and cleanup the script tags.
I propose to bump to critical because now
WebAssert::pageTextContainshaving replacedAssertLegacyTrait::assertText, we may have false negatives. It's also blocking replacement ofAssertLegacyTrait::assertNoTextin #3191935: Replace usages of AssertLegacyTrait::assertNoText, which is deprecated.Comment #27
mondrakeInstead of swapping the entire Session class like in #2784427: Browser tests: Plain page text contains CSS styles and JS settings, patch from MR is just cleaning up the page text from contents of the script tags before processing the WebAssert checks.
Comment #28
longwaveInteresting set of fails!
Worth adding some unit tests for that private method?
Comment #29
longwaveComment #30
mondrakeThe script cleanup does not work as expected, that may explain some fails. On it.
Comment #31
mondrakeYes, interesting failures...
Comment #32
mondrakeComment #33
mondrakeUhm, at least one FunctionaJavascript failure is due to the text still being present on the page, BUT its style attribute being set to
display: none. Changed one test to be more accurate anch check the attribute value rather thatn overall presence of text on the page.Will need to check the remaining issues.
Comment #34
longwaveMaybe out of scope for this but in FunctionalJavascript tests can we ask the browser for all the visible text?
Comment #35
alexpott> Maybe out of scope for this but in FunctionalJavascript tests can we ask the browser for all the visible text?
Yep we should be able to.
I still feel that this should be fixed upstream in https://github.com/minkphp/MinkBrowserKitDriver/issues/153
Comment #36
alexpottI think we should consider a different fix. We could alias and copy \Behat\Mink\Element\DocumentElement and add getText method that does:
Comment #37
alexpottHmmm.... after looking at this at bit I've found that the legacy methods didn't properly remove drupal settings from the text content either.
This bit from \Drupal\FunctionalTests\AssertLegacyTrait::assertText() / \Drupal\FunctionalTests\AssertLegacyTrait::assertNoText() did NOT remove the drupal settings json either.
Comment #38
jungleAs we have
symfony/dom-crawlerandsymfony/css-selectorin core, can we just use CSS selector to get rid of thescripttag in thebodywith selectorbody :not(script)or both thescripttag andstyletag (if it has) with the selectorbody :not(script):not(style). For example:See the attached file for the full demo.
Comment #39
mondrakeLooks good to me, @alexpott++
Comment #40
alexpott@jungle - I'm concerned about performance for doing that. To do that in a performant way we're going to need to swap out \Behat\Mink\Driver\BrowserKitDriver and do stuff and I'm not sure this is correct. I think this fix really belongs upstream in https://github.com/minkphp/MinkBrowserKitDriver/issues/153 but a work around for this in \Drupal\Tests\DocumentElement is acceptable - swapping out BrowserKitDriver is way more fraught as the scope is much larger.
Also trying to implement and making \Drupal\Tests\DocumentElement::getText
And running \Drupal\FunctionalTests\BrowserTestBaseTest::testGoTo() shows that the selector
'body :not(script)'unfortunately does not work as hoped.Comment #41
alexpottI changed
I think using Xss::filter() is unnecessary. We can call strip_tags() as that's what we want to do. The other features of Xss::filter() - fixing HTML entities / removing dangerous protocols I think get in the way of the test and shouldn't really be part of preparing the text output for browser testing.
Comment #42
jungle>I'm concerned about performance for doing that
Got it.
> $text = $crawler->filter('body :not(script)')->text();
@alexpott, it only returns the first child's text of the body. So in my example, called ->each() further to traverse all its children and concat the text.
> And running \Drupal\FunctionalTests\BrowserTestBaseTest::testGoTo() shows that the selector 'body :not(script)' unfortunately does not work as hoped.
Probably, the above is the reason.
Comment #43
alexpott@jungle yep you're correct... if I do
That everything works as expected.
I still think the solution in the patch is okay until we can land a much better fix upstream.
Comment #44
longwaveThe HTML5 spec lists what are considered "hidden elements": https://html.spec.whatwg.org/#hidden-elements
Do we need to consider any of these tags as well? Or is that out of scope for this issue?
Comment #45
alexpott@longwave I think we should solve the big issue at hand - ie. the inclusion of json and then try again upstream.
Comment #46
larowlanUpdating issue credits
Comment #49
larowlanMerged to 9.3.x
As this is a critical bug-fix, is test-code only and for testing system consistency, backported to 9.2.x
Comment #51
maosmurf commentedFWIW the change in f342570 has broken tests on my 9.2.1 after upgrade:
Caused by
tests/bootstrap.phpbecause I had no mink installed and therefore
\Drupal\Tests\DocumentElementcould not extend\Behat\Mink\Element\TraversableElement.Fixed by
composer require --dev behat/minklocally into the project.Comment #52
chegor commented#51 helped me.
Comment #53
capysara commentedThanks @maosmurf! I needed that.
Comment #54
claudiu.cristeaI have the same problem in my project with Behat tests and I badly need this class alias. First I thought it could alias in the project's composer.json, in
autoload-dev.files. That works for Behat tests but when I'm running the kernel/functional tests, I'm, obviously, getting:Would it be possible to:
core/composer.json, underautoload-dev.files(place it in a file)web/core/tests/bootstrap.phpcheck if the class has been aliased and only do the alias if notComment #55
fkelly12054@gmail.com commentedAfter upgrading from 9.5.4 to 9.5.5 (including fixing some composer validation errors in an attempt to lay the foundation for 10.0.5) I started to encounter the error listed here:
Error : Class "Behat\Mink\Driver\BrowserKitDriver" not found
Looking in my vendor directory, behat only had the geckodriver.exe program and no mink subdirectory. I ran the fix listed in #51 and now I have a mink subdirectory under the behat directory. There is a new line in composer.json after the require:
"require-dev": {
"behat/mink": "^1.10"
However, I still get the
Error : Class "Behat\Mink\Driver\BrowserKitDriver" not found message when doing PHPunit testing under PHPstorm. I have been running those tests successfully for several months trying to fix tests in a contrib module.
I can see that the patch listed in #47 has been applied. Looking on my hosted production system (where I don't run tests or need Behat) there is no behat directory at all under vendor.
Since it's been 8 months since the last post in this thread, I am wondering if this issue just resurfaced between 9.5.4 and 9.5.5. There has never been a response to #54 nor do I have any way to know if it would fix my immediate problem.
Edit: I don't think that behat/mink:^1.10 is the correct version. Researching that further.