Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Expectations/Tests with Blackfire Player

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

We just used blackfire-player to execute our first "scenario". It's pretty simple: it goes to the homepage then clicks the "Log In" link:

14 lines scenario.bkf
name "Various scenarios for the site"
# override with --endpoint option
endpoint "https://localhost:8000"
scenario
name "Basic Visit"
visit url("/")
name "Homepage"
click link("Log In")
name "Login page"

It works... but... we're not doing anything after we visit these pages. The true power of blackfire-player is that you can add tests to your scenario - or even scrape pages and save that data somewhere.

Adding an Expectation/Test to a Page

To add a "test" - or "assertion", or "expectation"... I love when things have 5 names... - say expect followed by - you guessed it! - an expression! status_code() == 200. Copy that and add it to the login page as well:

17 lines scenario.bkf
... lines 1 - 5
scenario
... lines 7 - 8
visit url("/")
name "Homepage"
expect status_code() == 200
click link("Log In")
name "Login page"
expect status_code() == 200
... lines 16 - 17

Ok, try blackfire-player again!

blackfire-player run scenario.bkf --ssl-no-verify -v

Woo! It still passes and now it's starting to be useful!

What's Possible in the expect Expression?

Let's break this down. First, just like we saw with the metrics stuff:

... lines 1 - 8
class MainControllerTest extends WebTestCase
{
... lines 11 - 27
public function testGetGitHubOrganizationBlackfireHttpRequests()
{
... lines 30 - 31
$blackfireConfig = (new Configuration())
->assert('metrics.http.requests.count == 1');
... lines 34 - 37
}
}

This is an expression - it's Symfony's ExpressionLanguage once again - basically JavaScript. And second... this expression has a ton of built-in functions.

Search the blackfire-player docs for "status_code"... and keep searching until you find a big function list. Here it is. Yep, we can use current_url(), header() to get a header value and many others. The css() function is especially useful: it allows us to dig into the HTML on the page. We'll use that in a minute. The docs also have good examples of how to do more complex things. But we're not going to become Blackfire player experts right now... I just want you to get comfortable with writing scenarios.

Asserting HTML Elements with css()

Let's try to write a failing expectation to see what it looks like. Let's see... we could find this table and assert that it has more than 500 rows... which it definitely does not. Let's find a CSS selector we can use... hmm. Ok, we could look for a <tbody> with this js-sightings-list class and then count its <tr> elements.

Back inside the scenario file, add another expect. This time use the css() function and pass it a CSS selector: tbody.js-sightings-list tr:

18 lines scenario.bkf
... lines 1 - 5
scenario
... lines 7 - 8
visit url("/")
name "Homepage"
expect status_code() == 200
expect css("tbody.js-sightings-list tr").count() > 500
... lines 13 - 18

Internally, The blackfire-player uses Symfony's Crawler object from the DomCrawler component, which has a count() method on it. Assert that this is > 500.

Let's see what happens!

blackfire-player run scenario.bkf --ssl-no-verify -v

And... yes! It fails - with a nice error:

The count() of that CSS element is 25, which is not greater than 500.

Go back and change this to 10:

18 lines scenario.bkf
... lines 1 - 5
scenario
... lines 7 - 8
visit url("/")
... lines 10 - 11
expect css("tbody.js-sightings-list tr").count() > 10
... lines 13 - 18

The data is dynamic data... so we don't really know how many rows it will have. But since our fixtures add more than 10 sightings... and because there will probably be at least 10 sightings if we ever ran this against production, this is probably a safe value.

Try it now:

blackfire-player run scenario.bkf --ssl-no-verify -v

All better!

Typos in Expressions

Another thing that blackfire-player does well is its errors when I... do something silly. Make a typo: change count() to ount():

18 lines scenario.bkf
... lines 1 - 5
scenario
... lines 7 - 8
visit url("/")
... lines 10 - 11
expect css("tbody.js-sightings-list tr").ount() > 10
... lines 13 - 18

And rerun the scenario:

blackfire-player run scenario.bkf --ssl-no-verify -v

Unable to call method ount of object Crawler.

That's a huge hint to tell you what object you're working with so you can figure out what methods it does have. Change that back to count():

18 lines scenario.bkf
... lines 1 - 5
scenario
... lines 7 - 8
visit url("/")
... lines 10 - 11
expect css("tbody.js-sightings-list tr").count() > 10
... lines 13 - 18

Performance Assertions in the Scenarios?

So... blackfire-player has nothing to do with the Blackfire profiler. It's just a useful tool for visiting pages, clicking on links and adding expectations. But... if it truly had nothing to do with the profiler, I probably wouldn't have talked about it. In reality, the concept of "scenarios" is about to become very important - it's a fundamental part of a topic we'll talk about soon: Blackfire "builds".

And actually, there is one little integration between blackfire-player and the profiler: you can add performance assertions to your scenario. To do that, instead of expect, say assert and then use any performance expression you want: the same strings that you can use inside a test. For example: metrics.sql.queries.count < 30:

20 lines scenario.bkf
... lines 1 - 5
scenario
... lines 7 - 8
visit url("/")
... lines 10 - 13
assert metrics.sql.queries.count < 30
... lines 15 - 20

When we execute this:

blackfire-player run scenario.bkf --ssl-no-verify -v

It does still pass. But if you played with this value - like set it to < 1 and re-ran the scenario:

blackfire-player run scenario.bkf --ssl-no-verify -v

Hmm, it still passes... even though this page is definitely making more than one query. The reason is that the assert functionality won't work inside a scenario until we introduce Blackfire "environments" - which we will soon. They are one of my absolute favorite parts of Blackfire.

For now, I'll leave a comment that this won't work until then:

20 lines scenario.bkf
... lines 1 - 5
scenario
... lines 7 - 8
visit url("/")
... lines 10 - 12
# won't work until we're using Blackfire environment
assert metrics.sql.queries.count < 30
... lines 15 - 20

Next, let's deploy to production! Because once our site is deployed, we can finally talk about cool things like "environments" and "builds". You can use anything to deploy, of course, but we will use SymfonyCloud.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial can be used to learn how to profile any app - including Symfony 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "blackfire/php-sdk": "^1.20", // v1.20.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // v1.8.0
        "doctrine/doctrine-bundle": "^1.6.10|^2.0", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
        "doctrine/orm": "^2.5.11", // v2.6.4
        "phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.2
        "sensio/framework-extra-bundle": "^5.4", // v5.5.1
        "symfony/console": "4.3.*", // v4.3.10
        "symfony/dotenv": "4.3.*", // v4.3.10
        "symfony/flex": "^1.9", // v1.18.7
        "symfony/form": "4.3.*", // v4.3.10
        "symfony/framework-bundle": "4.3.*", // v4.3.9
        "symfony/http-client": "4.3.*", // v4.3.10
        "symfony/property-access": "4.3.*", // v4.3.10
        "symfony/property-info": "4.3.*", // v4.3.10
        "symfony/security-bundle": "4.3.*", // v4.3.10
        "symfony/serializer": "4.3.*", // v4.3.10
        "symfony/twig-bundle": "4.3.*", // v4.3.10
        "symfony/validator": "4.3.*", // v4.3.10
        "symfony/webpack-encore-bundle": "^1.6", // v1.7.2
        "symfony/yaml": "4.3.*", // v4.3.10
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.2", // 3.2.2
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.9
        "fzaninotto/faker": "^1.8", // v1.8.0
        "symfony/browser-kit": "4.3.*", // v4.3.10
        "symfony/css-selector": "4.3.*", // v4.3.10
        "symfony/debug-bundle": "4.3.*", // v4.3.10
        "symfony/maker-bundle": "^1.13", // v1.14.3
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/phpunit-bridge": "^5.0", // v5.0.3
        "symfony/stopwatch": "4.3.*", // v4.3.10
        "symfony/var-dumper": "4.3.*", // v4.3.10
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.10
    }
}
userVoice