|  | # How to Extend the Web Test Framework | 
|  |  | 
|  | The Web Test Framework that Blink uses is a regression testing tool that is | 
|  | multi-platform and it has a large amount of tools that help test varying types | 
|  | of regression, such as pixel diffs, text diffs, etc. The framework is mainly | 
|  | used by Blink, however it was made to be extensible so that other projects can | 
|  | use it test different parts of chrome (such as Print Preview). This is a guide | 
|  | to help people who want to actually the framework to test whatever they want. | 
|  |  | 
|  | [TOC] | 
|  |  | 
|  | ## Background | 
|  |  | 
|  | Before you can start actually extending the framework, you should be familiar | 
|  | with how to use it. See the [web tests documentation](testing/web_tests.md). | 
|  |  | 
|  | ## How to Extend the Framework | 
|  |  | 
|  | There are two parts to actually extending framework to test a piece of software. | 
|  | The first part is extending certain files in: | 
|  | [/third_party/blink/tools/blinkpy/web_tests/](/third_party/blink/tools/blinkpy/web_tests/) | 
|  | The code in `blinkpy/web_tests` is the web test framework itself | 
|  |  | 
|  | The second part is creating a driver (program) to actually communicate the | 
|  | web test framework. This part is significantly more tricky and dependent on | 
|  | what exactly exactly is being tested. | 
|  |  | 
|  | ### Part 1 | 
|  |  | 
|  | This part isn’t too difficult. There are basically two classes that need to be | 
|  | extended (ideally, just inherited from). These classes are: | 
|  |  | 
|  | *   `Driver`. Located in `web_tests/port/driver.py`. Each instance of this is | 
|  | the class that will actually an instance of the program that produces the | 
|  | test data (program in Part 2). | 
|  | *   `Port`. Located in `web_tests/port/base.py`. This class is responsible | 
|  | creating drivers with the correct settings, giving access to certain OS | 
|  | functionality to access expected files, etc. | 
|  |  | 
|  | #### Extending Driver | 
|  |  | 
|  | As said, Driver launches the program from Part 2. Said program will communicate | 
|  | with the driver class to receive instructions and send back data. All of the | 
|  | work for driver gets done in `Driver.run_test`. Everything else is a helper or | 
|  | initialization function. | 
|  |  | 
|  | `run_test()` steps: | 
|  |  | 
|  | 1.  On the very first call of this function, it will actually run the test | 
|  | program. On every subsequent call to this function, at the beginning it will | 
|  | verify that the process doesn’t need to be restarted, and if it does, it | 
|  | will create a new instance of the test program. | 
|  | 1.  It will then create a command to send the program | 
|  | *   This command generally consists of an html file path for the test | 
|  | program to navigate to. | 
|  | *   After creating it, the command is sent | 
|  | 1.  After the command has been sent, it will then wait for data from the | 
|  | program. | 
|  | *   It will actually wait for 2 blocks of data. | 
|  | *   The first part being text or audio data. This part is required (the | 
|  | program will always send something, even an empty string) | 
|  | *   The second block is optional and is image data and an image hash | 
|  | (md5) this block of data is used for pixel tests | 
|  | 1.  After it has received all the data, it will proceed to check if the program | 
|  | has timed out or crashed, and if so fail this instance of the test (it can | 
|  | be retried later if need be). | 
|  |  | 
|  | Luckily, `run_test()` most likely doesn’t need to be overridden unless extra | 
|  | blocks of data need to be sent to/read from the test program. However, you do | 
|  | need to know how it works because it will influence what functions you need to | 
|  | override. Here are the ones you’re probably going to need to override | 
|  |  | 
|  | cmd_line | 
|  |  | 
|  | This function creates a set of command line arguments to run the test program, | 
|  | so the function will almost certainly need to be overridden. | 
|  |  | 
|  | It creates the command line to run the program. `Driver` uses `subprocess.popen` | 
|  | to create the process, which takes the name of the test program and any options | 
|  | it might need. | 
|  |  | 
|  | The first item in the list of arguments should be the path to test program using | 
|  | this function: | 
|  |  | 
|  | self._port._path_to_driver() | 
|  |  | 
|  | This is an absolute path to the test program. This is the bare minimum you need | 
|  | to get the driver to launch the test program, however if you have options you | 
|  | need to append, just append them to the list. | 
|  |  | 
|  | start | 
|  |  | 
|  | If your program has any special startup needs, then this will be the place to | 
|  | put it. | 
|  |  | 
|  | That’s mostly it. The Driver class has almost all the functionality you could | 
|  | want, so there isn’t much to override here. If extra data needs to be read or | 
|  | sent, extra data members should be added to `ContentBlock`. | 
|  |  | 
|  | #### Extending Port | 
|  |  | 
|  | This class is responsible for providing functionality such as where to look for | 
|  | tests, where to store test results, what driver to run, what timeout to use, | 
|  | what kind of files can be run, etc. It provides a lot of functionality, however | 
|  | it isn’t really sufficient because it doesn’t account of platform specific | 
|  | problems, therefore port itself shouldn’t be extend. Instead LinuxPort, WinPort, | 
|  | and MacPort (and maybe the android port class) should be extended as they | 
|  | provide platform specific overrides/extensions that implement most of the | 
|  | important functionality. While there are many functions in Port, overriding one | 
|  | function will affect most of the other ones to get the desired behavior. For | 
|  | example, if `web_tests_dir()` is overridden, not only will the code look for | 
|  | tests in that directory, but it will find the correct TestExpectations file, the | 
|  | platform specific expected files, etc. | 
|  |  | 
|  | Here are some of the functions that most likely need to be overridden. | 
|  |  | 
|  | *   `driver_class` | 
|  | *   This should be overridden to allow the testing program to actually run. | 
|  | By default the code will run content_shell, which might or might not be | 
|  | what you want. | 
|  | *   It should be overridden to return the driver extension class created | 
|  | earlier. This function doesn’t return an instance on the driver, just | 
|  | the class itself. | 
|  | *   `driver_name` | 
|  | *   This should return the name of the program test p. By default it returns | 
|  | ‘content_shell’, but you want to have it return the program you want to | 
|  | run, such as `chrome` or `browser_tests`. | 
|  | *   `web_tests_dir` | 
|  | *   This tells the port where to look for all the and everything associated | 
|  | with them such as resources files. | 
|  | *   By default it returns the absolute path to the web tests directory. | 
|  | *   If you are planning on running something in the chromium src/ directory, | 
|  | there are helper functions to allow you to return a path relative to the | 
|  | base of the chromium src directory. | 
|  |  | 
|  | The rest of the functions can definitely be overridden for your projects | 
|  | specific needs, however these are the bare minimum needed to get it running. | 
|  | There are also functions you can override to make certain actions that aren’t on | 
|  | by default always take place. For example, the web test framework always | 
|  | checks for system dependencies unless you pass in a switch. If you want them | 
|  | disabled for your project, just override `check_sys_deps` to always return OK. | 
|  | This way you don’t need to pass in so many switches. | 
|  |  | 
|  | As said earlier, you should override LinuxPort, MacPort, and/or WinPort. You | 
|  | should create a class that implements the platform independent overrides (such | 
|  | as `driver_class`) and then create a separate class for each platform specific | 
|  | port of your program that inherits from the class with the independent overrides | 
|  | and the platform port you want. For example, you might want to have a different | 
|  | timeout for your project, but on Windows the timeout needs to be vastly | 
|  | different than the others. In this case you can just create a default override | 
|  | that every class uses except your Windows port. In that port you can just | 
|  | override the function again to provide the specific timeout you need. This way | 
|  | you don’t need to maintain the same function on each platform if they all do the | 
|  | same thing. | 
|  |  | 
|  | For `Driver` and `Port` that’s basically it unless you need to make many odd | 
|  | modifications. Lots of functionality is already there so you shouldn’t really | 
|  | need to do much. | 
|  |  | 
|  | ### Part 2 | 
|  |  | 
|  | This is the part where you create the program that your driver class launches. | 
|  | This part is very application dependent, so it will not be a guide on how | 
|  | implement certain features, just what should be implemented and the order in | 
|  | which events should occur and some guidelines about what to do/not do. For a | 
|  | good example of how to implement your test program, look at MockDRT in | 
|  | `mock_drt.pyin` the same directory as `base.py` and `driver.py`. It goes through | 
|  | all the steps described below and is very clear and concise. It is written in | 
|  | python, but your driver can be anything that can be run by `subprocess.popen` | 
|  | and has stdout, stdin, stderr. | 
|  |  | 
|  | #### Goals | 
|  |  | 
|  | Your goal for this part of the project is to create a program (or extend a | 
|  | program) to interface with the web test framework. The web test framework | 
|  | will communicate with this program to tell it what to do and it will accept data | 
|  | from this program to perform the regression testing or create new base line | 
|  | files. | 
|  |  | 
|  | #### Structure | 
|  |  | 
|  | This is how your code should be laid out. | 
|  |  | 
|  | 1.  Initialization | 
|  | *   The creation of any directories or the launching of any programs should | 
|  | be done here and should be done once. | 
|  | *   After the program is initialized, “#READY\n” should be sent to progress | 
|  | the `run_test()` in the driver. | 
|  | 1.  Infinite Loop (!) | 
|  | *   After initialization, your program needs to actually wait for input, | 
|  | then process that input to carry out the test. In the context of web | 
|  | testing, the `content_shell` needs to wait for an html file to navigate | 
|  | to, render it, then convert that rendering to a PNG. It does this | 
|  | constantly, until a signal/message is sent to indicate that no more | 
|  | tests should be processed | 
|  | *   Details: | 
|  | *   The first thing you need is your test file path and any other | 
|  | additional information about the test that is required (this is sent | 
|  | during the write() step in `run_tests()` is `driver.py`. This | 
|  | information will be passed through stdin and is just one large | 
|  | string, with each part of the command being split with apostrophes | 
|  | (ex: “/path’foo” is path to the test file, then foo is some setting | 
|  | that your program might need). | 
|  | *   After that, your program should act on this input, how it does this | 
|  | is dependent on your program, however in `content_shell`, this would | 
|  | be the part where it navigates to the test file, then renders it. | 
|  | After the program acts on the input, it needs to send some text to | 
|  | the driver code to indicate that it has acted on the input. This | 
|  | text will indicate something that you want to test. For example, if | 
|  | you want to make sure you program always prints “foo” you should | 
|  | send it to the driver. If the program every prints “bar” (or | 
|  | anything else), that would indicate a failure and the test will | 
|  | fail. | 
|  | *   Then you need to send any image data in the same manner as you did | 
|  | for step 2. | 
|  | *   Cleanup everything related to processing the input from step i, then | 
|  | go back to step 1. | 
|  | *   This is where the ‘infinite’ loop part comes in, your program | 
|  | should constantly accept input from the driver until the driver | 
|  | indicates that there are no more tests to run. The driver does this | 
|  | by closing stdin, which will cause std::cin to go into a bad state. | 
|  | However, you can also modify the driver to send a special string | 
|  | such as ‘QUIT’ to exit the while loop. | 
|  |  | 
|  | That’s basically what the skeleton of your program should be. | 
|  |  | 
|  | ### Details | 
|  |  | 
|  | This is information about how to do some specific things, such as sending data | 
|  | to the web test framework. | 
|  |  | 
|  | *   Content Blocks | 
|  | *   The web test framework accepts output from your program in blocks of | 
|  | data through stdout. Therefore, printing to stdout is really sending | 
|  | data to the web test framework. | 
|  | *   Structure of block | 
|  | *   “Header: Data\n” | 
|  | *   Header indicates what type of data will be sent through. A list | 
|  | of valid headers is listed in `Driver.py`. | 
|  | *   Data is the data that you actually want to send. For pixel | 
|  | tests, you want to send the actual PNG data here. | 
|  | *   The newline is needed to indicate the end of a header. | 
|  | * End of a content block | 
|  | *   To indicate the end of a a content block and cause the driver to | 
|  | progress, you need to write “#EOF\n” to stdout (mandatory) and | 
|  | to stderr for certain types of content, such as image data. | 
|  | * Multiple headers per block | 
|  | *   Some blocks require different sets of data. For PNGs, not only | 
|  | is the PNG needed, but so is a hash of the bitmap used to create | 
|  | the PNG. | 
|  | *   In this case this is how your output should look. | 
|  | *   “Content-type: image/png\n” | 
|  | *   “ActualHash: hashData\n” | 
|  | *   “Content-Length: lengthOfPng\n” | 
|  | *   “pngdata” | 
|  | *   This part doesn’t need a header specifying that you are | 
|  | sending png data, just send it | 
|  | *   “#EOF\n” on both stdout and stderr | 
|  | *   To see the structure of the data required, look at the | 
|  | `read_block` functions in Driver.py |