Recorder tab on DevTools

Creating a recording

  1. Navigate to the page where you want to start the recording.
  2. Open the DevTools by secoundary-clicking on the page and selecting Inspect.
  3. Open the Recorder tab.
  4. Click the Create a new recording button.
  5. Enter a name for your user flow.
  6. Click Start a new recording.
  7. Go through the user journey on your page.
  8. Click End recording

Replay a recording

  • The Replay button simply performs the recorded steps, so you may verify your recording.
  • The Settings let you control the emulated network speed
    • Reducing the network speed to a slower connection is helpful when testing performance and capped speed in more consistent measurements.
  • If a replay isn’t successful, the step where the replay failed is highlighted in the user flow.
  • You can edit individual steps (e.g. choose another element selector).

Measure Performance

  • Captures a DevTools Performance profile while going through the user flow. You may undestand this way which parts of the process are slowing the user down.
    • Hovering over the filmstrip screenshots can give you an idea of what’s going on at any given point.
    • The CPU utilization timeline at the top of the page can point out potential JavaScript bottlenecks.

Export

  • DevTools can export your user journey as a Puppeteer script (e.g. demo_user_flow_recording.js).

Running a Puppeteer script

Basics

Puppeteer is a NodeJS library that lets you control a browser through code.

1
2
3
npm install puppeteer
# run a puppeteer script you exported
node demo_user_flow_recording.js

Exported file overview

  • Each step is wrapped in curly braces, separating the steps and creating a separate scope for variables.
  • waitForSelectors is called with multiple selectors, so if one selector doesn’t work there are others to fall back to, making the script less likely to break and easier to debug when it does
  • waitForSelectors uses Puppeteer’s custom query handlers, so the script looks for an element matching aria/Search rather than CSS selector
  • There’s some code to handle setting the value on non-standard (?) elements
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// launch headless
const browser = await puppeteer.launch();
const page = await browser.newPage();

{
const targetPage = page;
await targetPage.setViewport({"width":1135,"height":338})
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.goto('https://github.com/');
await Promise.all(promises);
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Search GitHub"],
["body > div.position-relative.js-header-wrapper > header > div > div.HeaderMenu.HeaderMenu--logged-out.position-fixed.top-0.right-0.bottom-0.height-fit.position-lg-relative.d-lg-flex.flex-justify-between.flex-items-center.flex-auto > div.d-lg-flex.flex-items-center.px-3.px-lg-0.text-center.text-lg-left > div.d-lg-flex.min-width-0.mb-3.mb-lg-0 > div > div > form > label > input.form-control.input-sm.header-search-input.jump-to-field.js-jump-to-field.js-site-search-focus.js-navigation-enable.jump-to-field-active.jump-to-dropdown-visible"]], targetPage);
await element.click({ offset: { x: 74.5, y: 24} });
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Search GitHub"],
["body > div.position-relative.js-header-wrapper > header > div > div.HeaderMenu.HeaderMenu--logged-out.position-fixed.top-0.right-0.bottom-0.height-fit.position-lg-relative.d-lg-flex.flex-justify-between.flex-items-center.flex-auto > div.d-lg-flex.flex-items-center.px-3.px-lg-0.text-center.text-lg-left > div.d-lg-flex.min-width-0.mb-3.mb-lg-0 > div > div > form > label > input.form-control.input-sm.header-search-input.jump-to-field.js-jump-to-field.js-site-search-focus.js-navigation-enable.jump-to-field-active.jump-to-dropdown-visible"]], targetPage);
const type = await element.evaluate(el => el.type);
if (["textarea","select-one","text","url","tel","search","password","number","email"].includes(type)) {
await element.type('react');
} else {
await element.focus();
await element.evaluate((el, value) => {
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}, "react");
}
}

Notes

  • Launch the script on normal chrome
    1
    const browser = await puppeteer.launch({ headless: false });

Puppeteer

  • Node.js library developed by Google
  • it controls headless Chrome through the DevTools Protocol
  • uses headless Chrome or Chromebit devices, without requiring any browser extensions like Selenium Webdriver or PhantomJS
  • E2E testing: run tests in the browser and then see the results in real-time on your terminal
  • uses the WebDriver protocol to connect with the browser and simulate user interaction with HTML elements or pages

Set Up

  1. app.js file
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const puppeteer = require('puppeteer');

    (async () => {
    const browser = await puppeteer.launch();
    // create a new page
    const page = await browser.newPage();
    // provide a new URL to browser.newPage()
    await page.goto('https://angelesbroullon-codenotepad.statichost.eu/');

    // close the running process
    await browser.close();
    })();
  2. run npm init to generate the package.json file
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "name": "hello",
    "version": "1.0.0",
    "description": "",
    "main": "app.js",
    "scripts": {
    "test": "echo \"Error not specified\" && exit 1"
    },
    "author": ""
    }

1st test

  1. Install packages

    1
    2
    3
    npm i puppeteer
    npm i mocha
    npm i selenium-webdriver
  2. Code take screenshots via webdriver

    1
    2
    // take a screenshot of the webpage you navigated to
    await page.screenshot({path: 'example.png'});
  3. Code create a PDF via webdriver

    1
    await page.pdf({ path: 'example.pdf' });
  4. Run the application

    1
    node app.js

Test your Setup

  • Create example.test.js, using mocha syntax

    1
    2
    3
    4
    5
    6
    7
    const puppeteer = require('puppeteer')

    describe("My first Setup Testing",()=>{
    it("Home landing page",async()=>{
    const browser = await puppeteer.launch({headless:false})
    });
    });
  • Run test

    1
    npm run test

Using one Browser Instance with Puppeteer

Run scripts in the headless Chrome browser and access the window object.

  • Testing apps that need access to web resources like localStorage or cookies.

To use one browser instance with Puppeteer, you just need to pass { headless: false } to the launch method.

  • It’s asynchronous so it won’t block the main thread and make your application unresponsive.
1
2
3
4
5
let browser; (async() => {
if(!browser) browser = await puppeteer.launch({
headless: false
}
);

Structure

1
2
3
+ extension_directory
|_ manifest.json
|_ main.js

Manifest file

Structure

Create manifest.json file.

1
2
3
4
5
{
"manifest_version":2,
"version":"1.0",
"name":"Test",
}

Load

  • Firefox

    1. In the address bar: about:debugging#/runtime/this-firefox.
    2. Click on Load Temporary Add-on.
    3. Choose the manifest.json file from the directory.
  • Chrome

    1. In the address bar: chrome://extensions.
    2. Enable Developer Mode and switch into it.
    3. Click the Load unpacked button and select the extension directory.

Add functionality

  • Content scripts can manipulate the Document Object Model of the web page, injecting JS (and CSS).
    • “matches”: list of domains and subdomains where the content script should be added
    • “js”: array of the JS files to be loaded.
1
2
3
4
5
6
7
8
9
10
11
{
"manifest_version":2,
"version":"1.0",
"name":"Test",
"content_scripts":[
{
"matches":["<all_urls>"],
"js":["main.js"]
}
]
}

Main script

Structure

Create main.js :

1
alert("The test extension is up and running")

Load

  • Reload the extension as seen on manifest.
  • Don’t forget to reload the extension anytime you edit the code

Customize examples

Change all the images of a webpage we visit to an image we choose

  1. Add an dummy_image.jpg file to the extension directory

  2. Modify main.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    console.log("The extension is up and running");

    // select all elememnt with the `img` tag
    var images = document.getElementsByTagName('img')

    for (elt of images){
    // alter all those images
    elt.src = '${browser.runtime.getURL("dummy_image.jpg")}';
    elt.alt = 'an alt text';
    }
  3. Modify manifest.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "manifest_version":2,
    "version":"1.0",
    "name":"Test",
    "content_scripts":[
    {
    "matches":["<all_urls>"],
    "js":["main.js"]
    }
    ],
    "web_accessible_resources": [
    "dummy_image.jpg"
    ]
    }
  4. Reload the extension and visit any URL you like.

Add icons to your extension

  1. Edit the manifest.json file
    1
    2
    3
    4
    5
    6
    {
    "icons": {
    "48": "icon-48.png",
    "96": "icon-96.png"
    }
    }

Add a toolbar button

  1. Add the icon files to the extension directory.
  2. Modify manifest.json
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "browser_action":{
    "default_icon":{
    "19":"icon-19.png",
    "38":"icon-38.png"
    }
    }
    }

Listen events for the toolbar button

  1. Create background.js.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // open a new tab...
    function openTab(){

    var newTab = browser.tabs.create({
    url:'https://angelesbroullon-codenotepad.statichost.eu/',
    active:true
    })
    }

    // when clicking
    browser.browserAction.onClicked.addListener(openTab)
  2. Edit manifest.json

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "background":{
    "scripts": ["background.js"]
    },
    "permissions": [
    "tabs"
    ]
    }
  3. Reload the extension

Full example

Copy snippets from stack overflow

  • manifest.json (only work on stackoverflow domain)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "manifest_version":2,
    "version":"1.0",
    "name":"copy code",
    "content_scripts":[
    {
    "matches":["*://*.stackoverflow.com/*"],
    "js":["main.js"]
    }
    ]
    }
  • main.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // snippets are tagged as `s-code-block`
    var arr =document.getElementsByClassName("s-code-block");

    for(let i = 0 ; i < arr.length ; i++){
    // create and append the button
    var btn = document.createElement("button");
    btn.classList.add("copy_code_button");
    btn.appendChild(document.createTextNode("Copy"));
    arr[i].appendChild(btn);
    //styling the button
    btn.style.position = "relative";

    if(arr[i].scrollWidth === arr[i].offsetWidth
    && arr[i].scrollHeight === arr[i].offsetHeight) {
    btn.style.left = '${arr[i].offsetWidth - 70}px';
    } else if(arr[i].scrollWidth != arr[i].offsetWidth
    && arr[i].scrollHeight === arr[i].offsetWidth) {
    btn.style.left = '${arr[i].offsetWidth - 200}px';
    } else {
    btn.style.left = '${arr[i].offsetWidth - 150}px';
    }

    if(arr[i].scrollHeight === arr[i].offsetHeight) {
    btn.style.bottom = '${arr[i].offsetHeight - 50}px';
    } else {
    btn.style.bottom = '${arr[i].scrollHeight - 50}px';
    //end of styling the button
    }
    console.log("Appended");
    }

    // do the copy action when clicked
    // select all buttons
    var button = document.querySelectorAll(".copy_code_button")
    button.forEach((elm)=>{
    // listen to 'click' action
    elm.addEventListener('click',(e)=>{
    navigator.clipboard.writeText(elm.parentNode.childNodes[0].innerText);
    alert("Copied to Clipboard");
    })
    })

Simple example

  1. Get data

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # example from __main__.py
    import cProfile
    from pstats import Stats, SortKey

    from my_module.script import add_func


    def run_script():
    # call your function with some dummy data
    print(add_func(2,1))


    if __name__ == '__main__':
    do_profiling = True
    if do_profiling:
    with cProfile.Profile() as pr:
    run_script()

    with open('profiling_stats.txt', 'w') as stream:
    stats = Stats(pr, stream=stream)
    stats.strip_dirs()
    stats.sort_stats('time')
    stats.dump_stats('.prof_stats')
    stats.print_stats()
    else:
    start_game()
  2. Render data

Extensions on VSCodium

SYMFONY 2.8

Structure

Check doc, packagist.

  • Web

    • bundles (symlink)
      • Framework (CSS, JS, libraries)
        • SENSIO… (name can change)
  • Vendor (libs)

    • COMPOSER (similar to maven + stage)
    • DOCTRINE (similar to Hibernate)
    • IRCMAXWELL (passwords)
    • MONOLOG (logger_default)
    • PSR (logger format)
    • SENSIO (monolitic skeleton)
    • SWIFTMAILER (email)
    • SYMFONY
    • TWIG (templates to render JSF)
    • PHPUNIT (unit tests)
    • FORESTBUNDLE (generate REST app)
    • JMSSERIALIZER (serialize and deserialize)
    • JWTAUTHENTICATOR (Json web token, autenticator, session)
  • BIN (console for Symfony, internal, clean cache)

  • APP (internal config)

    • CACHE
    • LOG
    • RESOURCES (static at data level)
      • templates
      • translation
      • menu
    • CONFIG
      • security (firewall)
      • routing
      • routingdev (extends routing with profiles)
  • SRC (bundles)

    • APPBUNDLE (business modules, this is the main)

SYMFONY 3.4

Features

  • Microservice oriented.
    • Uses recipes, gets only the resources required.
  • Standard structure (UNIX).
  • Check xampp, composer, symfony for more info.

Changes

  • /var: cache log, session.
  • /web: no symlink, generated on compile.
  • /test: not on AppbBundle anymore.
  • /AppBundle/Datafeatures/: baseline for testing.
  • /AppBundle/Entity/: similar to Hibernate, classes that matches tables.
  • /AppBundle/EventListeners/: listens to events.
    • listeners: triggers.
    • subscribers: background task.

SYMFONY: CACHE

  • Console: fo to project folder.

    1
    2
    3
    cd app
    php console cache: clear
    php console cahe. clear --env=prod
  • You can check app /cache/dev/appDevDebugProjectContainerUrlGenerator.php

  • Tip: go to dev environment and review app_dev.pho/hello to be able to debug the URL.

0%