Full Code of beyondcode/dusk-dashboard for AI

master b0a018627be0 cached
36 files
72.5 KB
17.7k tokens
186 symbols
1 requests
Download .txt
Repository: beyondcode/dusk-dashboard
Branch: master
Commit: b0a018627be0
Files: 36
Total size: 72.5 KB

Directory structure:
gitextract_fazur9k6/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── .scrutinizer.yml
├── .styleci.yml
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config/
│   └── dusk-dashboard.php
├── phpunit.xml.dist
├── resources/
│   └── views/
│       └── index.html
└── src/
    ├── Action.php
    ├── BrowserActionCollector.php
    ├── Console/
    │   └── StartDashboardCommand.php
    ├── Dusk/
    │   ├── Browser.php
    │   └── Concerns/
    │       ├── InteractsWithAuthentication.php
    │       ├── InteractsWithCookies.php
    │       ├── InteractsWithElements.php
    │       ├── InteractsWithJavascript.php
    │       ├── InteractsWithMouse.php
    │       ├── MakesAssertions.php
    │       ├── MakesUrlAssertions.php
    │       └── WaitsForElements.php
    ├── DuskDashboardServiceProvider.php
    ├── DuskProcessFactory.php
    ├── Ratchet/
    │   ├── Http/
    │   │   ├── Controller.php
    │   │   ├── DashboardController.php
    │   │   └── EventController.php
    │   ├── Server/
    │   │   ├── App.php
    │   │   └── HttpServer.php
    │   └── Socket.php
    ├── Testing/
    │   └── TestCase.php
    └── Watcher.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org

root = true

[*]
charset = utf-8
indent_size = 4
indent_style = space
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore all test and documentation with "export-ignore".
/.gitattributes     export-ignore
/.gitignore         export-ignore
/.travis.yml        export-ignore
/phpunit.xml.dist   export-ignore
/.scrutinizer.yml   export-ignore
/tests              export-ignore
/.editorconfig      export-ignore


================================================
FILE: .gitignore
================================================
build
composer.lock
docs
vendor
coverage

================================================
FILE: .scrutinizer.yml
================================================
filter:
    excluded_paths: [tests/*]

checks:
    php:
        remove_extra_empty_lines: true
        remove_php_closing_tag: true
        remove_trailing_whitespace: true
        fix_use_statements:
            remove_unused: true
            preserve_multiple: false
            preserve_blanklines: true
            order_alphabetically: true
        fix_php_opening_tag: true
        fix_linefeed: true
        fix_line_ending: true
        fix_identation_4spaces: true
        fix_doc_comments: true



================================================
FILE: .styleci.yml
================================================
preset: laravel

disabled:
  - single_class_element_per_statement


================================================
FILE: .travis.yml
================================================
language: php

php:
  - 7.1
  - 7.2
  - 7.3

env:
  matrix:
    - COMPOSER_FLAGS="--prefer-lowest"
    - COMPOSER_FLAGS=""

before_script:
  - travis_retry composer self-update
  - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source

script:
  - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover

after_script:
  - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to `dusk-dashboard` will be documented in this file

## 1.0.0 - 201X-XX-XX

- initial release


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Contributions are **welcome** and will be fully **credited**.

Please read and understand the contribution guide before creating an issue or pull request.

## Etiquette

This project is open source, and as such, the maintainers give their free time to build and maintain the source code
held within. They make the code freely available in the hope that it will be of use to other developers. It would be
extremely unfair for them to suffer abuse or anger for their hard work.

Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
world that developers are civilized and selfless people.

It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.

## Viability

When requesting or submitting new features, first consider whether it might be useful to others. Open
source projects are used by many developers, who may have entirely different needs to your own. Think about
whether or not your feature is likely to be used by other users of the project.

## Procedure

Before filing an issue:

- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.

Before submitting a pull request:

- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.

## Requirements

If the project maintainer has any additional requirements, you will find them listed here.

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.

**Happy coding**!


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) Beyond Code GmbH <hello@beyondco.de>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# Laravel Dusk Dashboard

A beautiful dashboard for your Dusk test suites.

[![Latest Version on Packagist](https://img.shields.io/packagist/v/beyondcode/dusk-dashboard.svg?style=flat-square)](https://packagist.org/packages/beyondcode/dusk-dashboard)
[![Quality Score](https://img.shields.io/scrutinizer/g/beyondcode/dusk-dashboard.svg?style=flat-square)](https://scrutinizer-ci.com/g/beyondcode/dusk-dashboard)
[![Total Downloads](https://img.shields.io/packagist/dt/beyondcode/dusk-dashboard.svg?style=flat-square)](https://packagist.org/packages/beyondcode/dusk-dashboard)

![](https://pociot.dev/storage/22/dusk-dashboard.gif)

## Installation

You can install the package via composer:

```bash
composer require beyondcode/dusk-dashboard --dev
```

Next up, you need to go to your `DuskTestCase.php` that was installed by Laravel Dusk. You can find this file in your `tests` directory:

Find and replace this line:
```php
use Laravel\Dusk\TestCase as BaseTestCase;
```
with:
```php
use BeyondCode\DuskDashboard\Testing\TestCase as BaseTestCase;
```

## Usage

```
php artisan dusk:dashboard
```

Check out the documentation [here](https://pociot.dev/8-introducing-laravel-dusk-dashboard).

### Handling Asset Paths

Assets may not load or display properly when using relative paths due to port specification. Using Larvel's [Path Helpers](https://laravel.com/docs/5.7/helpers#available-methods) such as the `url()` and `asset()` helpers  (Ex: `{{ url('path/to/asset.css') }}`) will help overcome these pathing issues.

### Testing

``` bash
composer test
```

### Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.

## Contributing

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

### Security

If you discover any security related issues, please email marcel@beyondco.de instead of using the issue tracker.

## Credits

- [Marcel Pociot](https://github.com/mpociot)
- [All Contributors](../../contributors)

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.


================================================
FILE: composer.json
================================================
{
    "name": "beyondcode/dusk-dashboard",
    "description": "A beautiful dashboard for your Laravel Dusk tests",
    "keywords": [
        "beyondcode",
        "dusk-dashboard"
    ],
    "homepage": "https://github.com/beyondcode/dusk-dashboard",
    "license": "MIT",
    "authors": [
        {
            "name": "Marcel Pociot",
            "email": "marcel@beyondco.de",
            "homepage": "https://beyondcode.de",
            "role": "Developer"
        }
    ],
    "require": {
        "php": "^7.2",
        "cboden/ratchet": "^0.4.1",
        "clue/buzz-react": "^2.5",
        "guzzlehttp/guzzle": "^7.0.1",
        "illuminate/console": "5.6.*|5.7.*|5.8.*|6.*|7.*|8.*",
        "illuminate/support": "5.6.*|5.7.*|5.8.*|6.*|7.*|8.*",
        "laravel/dusk": "^4.0|^5.0|^6.0",
        "yosymfony/resource-watcher": "^2.0"
    },
    "require-dev": {
        "larapack/dd": "^1.0",
        "phpunit/phpunit": "^7.0"
    },
    "autoload": {
        "psr-4": {
            "BeyondCode\\DuskDashboard\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "BeyondCode\\DuskDashboard\\Tests\\": "tests"
        }
    },
    "scripts": {
        "test": "vendor/bin/phpunit",
        "test-coverage": "vendor/bin/phpunit --coverage-html coverage"

    },
    "config": {
        "sort-packages": true
    },
    "extra": {
        "laravel": {
            "providers": [
                "BeyondCode\\DuskDashboard\\DuskDashboardServiceProvider"
            ]
        }
    }
}


================================================
FILE: config/dusk-dashboard.php
================================================
<?php

return [
    /*
     * You may specify which host the dashboard runs on in case it differs from the APP_URL
     */
    'host' => env('DUSK_DASHBOARD_URL', env('APP_URL', 'http://localhost')),
];


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         verbose="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="BeyondCode Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">src/</directory>
        </whitelist>
    </filter>
    <logging>
        <log type="tap" target="build/report.tap"/>
        <log type="junit" target="build/report.junit.xml"/>
        <log type="coverage-html" target="build/coverage" charset="UTF-8" yui="true" highlight="true"/>
        <log type="coverage-text" target="build/coverage.txt"/>
        <log type="coverage-clover" target="build/logs/clover.xml"/>
    </logging>
</phpunit>


================================================
FILE: resources/views/index.html
================================================
<!doctype html>
<html class="h-full">
<head>
    <!-- Meta Information -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">

    <title>Dusk Dashboard</title>
</head>
<body class="h-full">
<div id="dashboard" class="max-h-screen h-screen">
    <div class="h-full">
        <div class="flex items-center h-16 px-2 w-full absolute pin-t" style="z-index: 900">
            <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><defs><linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%"><stop stop-color="#C7CECB" offset="0%"/><stop stop-color="#9CA5A0" offset="100%"/></linearGradient></defs><path fill="url(#a)" fill-rule="evenodd" d="M24 44a20 20 0 1 1 0-40 20 20 0 0 1 0 40zm9.7-10.17a13.97 13.97 0 0 0 2.88-4.18 14 14 0 0 1-18.5-18.5A14 14 0 1 0 33.7 33.83zm-14.14-5.65a16 16 0 0 0 12.29 4.66A12 12 0 0 1 14.9 15.89a16 16 0 0 0 4.66 12.29z"/></svg>

            <h4 class="mb-0 ml-3"><strong>Laravel</strong> Dusk Dashboard</h4>

            <button class="bg-transparent hover:bg-blue-500 text-blue-600 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded ml-auto mr-3"
                    @click.prevent="startTests" title="Start Tests">
                <span v-if="testsRunning">Watching...</span>
                <span v-else>Start Watching</span>
            </button>
        </div>
        <div class="flex h-full w-full absolute pt-16 pin-t">
            <div class="w-1/4 h-full overflow-y-scroll">
                <div class="block sticky ml-4">

                    <div class="inline-flex mb-4 items-center justify-center w-full">
                        <button @click="filter = null" class="focus:outline-none hover:bg-gray-500 text-gray-800 font-bold py-2 px-4 rounded-l" :class="{
                            'bg-gray-500': filter === null,
                            'bg-gray-400': filter !== null
                        }">
                            All ({{ _.size(groupedEvents) }})
                        </button>
                        <button @click="filter = 'success'" class="focus:outline-none hover:bg-gray-500 text-gray-800 font-bold py-2 px-4 border-l border-r border-gray-500" :class="{
                            'bg-gray-500': filter === 'success',
                            'bg-gray-400': filter !== 'success'
                        }">
                            Success ({{ _.size(success) }})
                        </button>
                        <button @click="filter = 'failure'" class="focus:outline-none hover:bg-gray-500 text-gray-800 font-bold py-2 px-4 rounded-r" :class="{
                            'bg-gray-500': filter === 'failure',
                            'bg-gray-400': filter !== 'failure'
                        }">
                            Failures ({{ _.size(failures) }})
                        </button>
                    </div>

                    <ul class="list-reset">
                        <li class="list-group-item" v-for="(events, test) in filteredEvents">
                            <p class="my-3 lg:mb-2 text-gray-500 uppercase tracking-wider font-bold text-sm lg:text-xs"
                               @click="expand(test)"
                               style="white-space: nowrap"
                               :class="{
                                                'text-green-600': ! events[events.length-1].failure,
                                                'text-red-600': events[events.length-1].failure,
                                            }">
                                <i class="fa fa-check" :class="{
                                                'text-green-600 fa-check': ! events[events.length-1].failure,
                                                'text-red-600 fa-times': events[events.length-1].failure,
                                            }"></i>
                                {{ test }}
                                    <i class="fa fa-chevron-down"
                                          v-show="!isExpanded(test)">
                                    </i>
                                    <i class="fa fa-chevron-up"
                                          v-show="isExpanded(test)">
                                    </i>
                            </p>

                            <ul class="list-reset mr-4 font-mono text-xs"
                                v-show="isExpanded(test)">
                                <li class="cursor-pointer bg-gray-400 hover:bg-gray-100 p-2"
                                    v-for="(event, key) in events"
                                    @mouseover="hoverEvent(event)"
                                    @click="pinEvent(event)"
                                    :class="{'bg-gray-100': event === pinnedEvent}">
                                    <div class="flex items-center">
                                        <div class="mr-2 flex items-center">
                                            <div class="font-mono text-gray-600 pr-2" style="min-width: 25px;">
                                                <i class="fa fa-thumbtack text-gray-500" v-if="event === hoveredEvent || event === pinnedEvent"></i>
                                                <span v-else>{{ key+1 }}</span>
                                            </div>
                                            <i class="fa fa-check" :class="{
                                                'text-green-600 fa-check': ! event.failure,
                                                'text-red-600 fa-times': event.failure,
                                            }"></i>
                                        </div>
                                        <span style="min-width: 180px;">{{ event.name }}</span>
                                        <span>{{ event.arguments[0] || '' }}</span>
                                    </div>
                                    <div class="flex flex-col my-2" v-if="event === pinnedEvent && event.arguments.length > 1">
                                        <div class="my-2" v-for="argument in event.arguments">
                                            {{ argument }}
                                        </div>
                                    </div>
                                    <div class="mt-2 w-full text-red-800" v-if="event.failure">
                                        {{ event.failure }}
                                    </div>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="w-3/4 bg-gray-500 h-full flex justify-center">
                <div class="mt-4 flex flex-col shadow-2xl" style="width: 1000px; height: 680px;">
                    <div class="rounded-sm bg-gray-400 p-2 h-12 justify-center items-center">
                        <input type="text" class="focus:outline-none h-full w-full font-mono p-2" v-model="activeEvent.path" readonly>
                    </div>
                    <div class="h-full w-full flex flex-col">
                        <iframe ref="iframe" id="iframe" frameborder="0" class="h-full w-full block bg-white overflow-auto"></iframe>
                        <div class="flex bg-gray-600 text-white rounded-sm h-12 justify-center items-center text-center" v-if="activeEvent">
                            <span class="mr-4">DOM Snapshot</span>
                            <a  v-if="activeEvent.before" class="mx-2 rounded bg-white text-gray-900 border-gray-400 shadow p-2 no-underline" href="#" @click="loadBefore">Before</a>
                            <a  v-if="activeEvent.before" class="mx-2 rounded bg-white text-gray-900 border-gray-400 shadow p-2 no-underline" href="#" @click="loadAfter">After</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

<script>
    var conn;

    new Vue({
        el: '#dashboard',

        data: {
            filter: null,
            activeEvent: {},
            hoveredEvent: null,
            pinnedEvent: null,
            events: [],
            expanded: [],
            testsRunning : false,
            currentTestName : '',
            timer: null,
        },

        computed: {
            groupedEvents: function() {
                return _.groupBy(this.events, 'test');
            },

            filteredEvents: function() {
                if (this.filter === 'failure') {
                    return this.failures;
                } else if (this.filter === 'success') {
                    return this.success;
                }
                return this.groupedEvents;
            },

            failures: function() {
                return _.pickBy(this.groupedEvents, (values, key) => {
                    return values[values.length -1].failure;
                });
            },

            success: function() {
                return _.pickBy(this.groupedEvents, (values, key) => {
                    return ! values[values.length -1].failure;
                });
            }
        },

        methods: {
            expand(key) {
                if (!this.expanded.includes(key)) {
                    this.expanded.push(key);
                } else {
                    for( var i = 0; i < this.expanded.length; i++){
                        if ( this.expanded[i] === key) {
                            this.expanded.splice(i, 1);
                        }
                    }
                }
            },

            isExpanded(key)
            {
                if (this.expanded.includes(key)) {
                    return true
                }
                return false;
            },

            hoverEvent: function(event) {
                this.hoveredEvent = event;
            },

            loadEvent: function(event) {
                if (this.activeEvent !== event) {
                    this.activeEvent = event;
                    this.loadHtml(event.html);
                }
            },

            pinEvent: function(event) {
                if (this.pinnedEvent === event) {
                    this.pinnedEvent = null;
                } else {
                    this.pinnedEvent = event;
                    this.loadEvent(event);
                }
            },

            startTests: function() {
                this.testsRunning = true;
                this.events = [];

                conn.send(JSON.stringify({
                    method: 'startTests'
                }));
            },

            loadBefore: function() {
                this.loadHtml(this.activeEvent.before);
            },

            loadAfter: function() {
                this.loadHtml(this.activeEvent.html);
            },

            loadHtml: function(html) {
                var iframeDocument = window.document.getElementById('iframe').contentWindow.document;

                iframeDocument.open('text/html', 'replace');
                iframeDocument.write(html);
                iframeDocument.close();

                setTimeout(function () {
                    this.highlightEvent(this.activeEvent);
                }.bind(this), 50);
            },

            highlightEvent: function(event) {
                let name = event.name;
                let selector;

                switch (name) {
                    case 'click':
                        selector = event.arguments[0];
                        break;
                    case 'select':
                    case 'radio':
                    case 'type':
                        selector = `#${event.arguments[0]},`;
                        selector += `input[name="${event.arguments[0]}"],`;
                        selector += `select[name="${event.arguments[0]}"],`;
                        selector += `textarea[name="${event.arguments[0]}"]`;
                        break;
                    case 'press':
                        selector = '';
                        selector += `input[type=submit][name='${event.arguments[0]}'],`;
                        selector += `input[type=submit][value='${event.arguments[0]}'],`;
                        selector += `input[type=button][value='${event.arguments[0]}'],`;
                        selector += `button[name='${event.arguments[0]}'],`;
                        selector += `button:contains('${event.arguments[0]}')`;
                        break;
                    case 'mouseover':
                        selector = event.arguments[0];
                        break;
                }

                if (selector) {
                    try {
                        document.getElementById('iframe')
                            .contentWindow
                            .jQuery(selector)
                            .css('outline', '5px solid red');
                    } catch (e) {}
                }
            }
        },

        mounted: function() {
            conn = new WebSocket(`ws://${window.location.hostname}:${window.location.port}/socket`);

            conn.onmessage = (e) => {
                let message = JSON.parse(e.data);

                window.clearTimeout(this.timer);
                this.timer = setTimeout(function() {
                    alert("Finished");
                    }, 2000);

                if (message.data.test !== this.currentTestName) {
                    // new test has started
                }
                this.currentTestName = message.data.test;

                if (message.name === 'dusk-event') {
                    this.events.push(message.data);

                    if (this.pinnedEvent === null) {
                        this.loadEvent(message.data);
                    }


                }

                if (message.name === 'dusk-failure') {
                    this.events[(this.events.length -1 )].failure = message.data.message;
                }

                if (message.name === 'dusk-reset') {
                    this.events = [];
                }
            };
        }
    });
</script>
</body>
</html>


================================================
FILE: src/Action.php
================================================
<?php

namespace BeyondCode\DuskDashboard;

class Action
{
    protected $name;

    protected $arguments;

    protected $html;

    protected $previousHtml;

    protected $path;

    public function __construct(string $name, array $arguments, string $html, string $path)
    {
        $this->name = $name;

        $this->arguments = $arguments;

        $this->html = $html;

        $this->path = $path;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getHtml()
    {
        return $this->html;
    }

    public function setPreviousHtml(?string $html)
    {
        $this->previousHtml = $html;
    }

    public function getPreviousHtml()
    {
        return $this->previousHtml;
    }

    public function getArguments()
    {
        return $this->arguments;
    }

    public function getPath()
    {
        return $this->path;
    }
}


================================================
FILE: src/BrowserActionCollector.php
================================================
<?php

namespace BeyondCode\DuskDashboard;

use BeyondCode\DuskDashboard\Console\StartDashboardCommand;
use BeyondCode\DuskDashboard\Dusk\Browser;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;

class BrowserActionCollector
{
    /** @var Client */
    protected $client;

    protected $testName;

    public function __construct($testName)
    {
        $this->testName = $testName;

        $this->client = new Client();
    }

    public function collect(string $action, array $arguments, Browser $browser, string $previousHtml = null)
    {
        $path = parse_url($browser->driver->getCurrentURL(), PHP_URL_PATH) ?? '';

        $action = new Action($action, $arguments, $browser->getCurrentPageSource(), $path);

        $action->setPreviousHtml($previousHtml);

        $this->pushAction('dusk-event', [
            'test' => $this->testName,
            'path' => $action->getPath(),
            'name' => $action->getName(),
            'arguments' => $action->getArguments(),
            'before' => $action->getPreviousHtml(),
            'html' => $action->getHtml(),
        ]);

        $this->processPerformanceLog($browser);
    }

    protected function processPerformanceLog(Browser $browser)
    {
        $logs = collect([]);

        try {
            $logs = collect($browser->driver->manage()->getLog('performance'));
        } catch (\Exception $e) {
            // performance logging might be disabled.
        }

        $allowedMethods = [
            'Network.requestWillBeSent',
            'Network.responseReceived',
        ];

        $logs
            ->map(function ($log) {
                return json_decode($log['message']);
            })
            ->filter(function ($log) use ($allowedMethods) {
                $method = data_get($log, 'message.method');

                $type = data_get($log, 'message.params.type');

                return in_array($method, $allowedMethods) && $type === 'XHR';
            })->groupBy(function ($log) {
                if (data_get($log, 'message.method') === 'Network.requestWillBeSent') {
                    return data_get($log, 'message.requestId');
                }

                return data_get($log, 'params.requestId');
            })->map(function ($log) use ($browser) {
                $this->pushPerformanceLog($log->toArray(), $browser);
            });
    }

    protected function pushAction(string $name, array $payload)
    {
        try {
            $this->client->post('http://127.0.0.1:'.StartDashboardCommand::PORT.'/events', [
                RequestOptions::JSON => [
                    'channel' => 'dusk-dashboard',
                    'name' => $name,
                    'data' => $payload,
                ],
            ]);
        } catch (\Exception $e) {
            // Dusk-Dashboard Server might be turned off. No need to panic!
        }
    }

    protected function pushPerformanceLog(array $log, Browser $browser)
    {
        $request = $log[0];
        $response = $log[1];

        $url = parse_url(data_get($request, 'message.params.request.url'));

        $this->pushAction('dusk-event', [
            'test' => $this->testName,
            'name' => 'XHR',
            'arguments' => [
                data_get($request, 'message.params.request.method').' '.
                $url['path'].' '.
                data_get($response, 'message.params.response.status').' '.
                data_get($response, 'message.params.response.statusText'),
            ],
            'html' => $browser->getCurrentPageSource(),
            'logs' => $log,
        ]);
    }
}


================================================
FILE: src/Console/StartDashboardCommand.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Console;

use BeyondCode\DuskDashboard\DuskProcessFactory;
use BeyondCode\DuskDashboard\Ratchet\Http\DashboardController;
use BeyondCode\DuskDashboard\Ratchet\Http\EventController;
use BeyondCode\DuskDashboard\Ratchet\Server\App;
use BeyondCode\DuskDashboard\Ratchet\Socket;
use BeyondCode\DuskDashboard\Watcher;
use Clue\React\Buzz\Browser;
use Illuminate\Console\Command;
use Ratchet\WebSocket\WsServer;
use React\EventLoop\Factory as LoopFactory;
use React\EventLoop\LoopInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Routing\Route;

class StartDashboardCommand extends Command
{
    const PORT = 9773;

    protected $signature = 'dusk:dashboard';

    protected $description = 'Start the Laravel Dusk Dashboard';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();

        $this->ignoreValidationErrors();
    }

    /** @var App */
    protected $app;

    /** @var LoopInterface */
    protected $loop;

    public function handle()
    {
        $url = parse_url(config('dusk-dashboard.host', config('app.url')));

        $this->loop = LoopFactory::create();

        $this->loop->futureTick(function () use ($url) {
            $dashboardUrl = 'http://'.$url['host'].':'.self::PORT.'/dashboard';

            $this->info('Started Dusk Dashboard on port '.self::PORT);

            $this->info('Your Dusk tests are now being watched.');

            $this->info('If the dashboard does not automatically open, visit: '.$dashboardUrl);

            $this->tryToOpenInBrowser($dashboardUrl);
        });

        $this->createTestWatcher();

        $this->createApp($url);
    }

    protected function addRoutes()
    {
        $eventRoute = new Route('/events', ['_controller' => new EventController()], [], [], null, [], ['POST']);

        $this->app->routes->add('events', $eventRoute);

        $dashboardRoute = new Route('/dashboard', ['_controller' => new DashboardController()], [], [], null, [], ['GET']);

        $this->app->routes->add('dashboard', $dashboardRoute);
    }

    protected function createTestWatcher()
    {
        $finder = (new Finder)
            ->name('*.php')
            ->files()
            ->in($this->getTestSuitePath());

        (new Watcher($finder, $this->loop))->startWatching(function () {
            $client = new Browser($this->loop);

            $client->post('http://127.0.0.1:'.self::PORT.'/events', [
                'Content-Type' => 'application/json',
            ], json_encode([
                'channel' => 'dusk-dashboard',
                'name' => 'dusk-reset',
                'data' => [],
            ])
            );

            $process = DuskProcessFactory::make();

            $process->start();
        });
    }

    protected function getTestSuitePath()
    {
        $directories = [];

        if (file_exists(base_path('phpunit.dusk.xml'))) {
            $xml = simplexml_load_file(base_path('phpunit.dusk.xml'));

            foreach ($xml->testsuites->testsuite as $testsuite) {
                $directories[] = (string) $testsuite->directory;
            }
        } else {
            $directories[] = base_path('tests/Browser');
        }

        return $directories;
    }

    protected function createApp(array $url)
    {
        $socket = new Socket();

        $this->app = new App($url['host'], self::PORT, '0.0.0.0', $this->loop);

        $this->app->route('/socket', new WsServer($socket), ['*']);

        $this->addRoutes();

        $this->app->run();
    }

    protected function tryToOpenInBrowser($url)
    {
        if (PHP_OS === 'Darwin') {
            exec('open '.$url);
        }
    }
}


================================================
FILE: src/Dusk/Browser.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk;

use BeyondCode\DuskDashboard\BrowserActionCollector;

class Browser extends \Laravel\Dusk\Browser
{
    use Concerns\InteractsWithAuthentication,
        Concerns\InteractsWithCookies,
        Concerns\InteractsWithElements,
        Concerns\InteractsWithJavascript,
        Concerns\InteractsWithMouse,
        Concerns\MakesAssertions,
        Concerns\MakesUrlAssertions,
        Concerns\WaitsForElements;

    /** @var BrowserActionCollector */
    protected $actionCollector;

    public function setActionCollector(BrowserActionCollector $collector)
    {
        $this->actionCollector = $collector;
    }

    /**
     * @return BrowserActionCollector|null
     */
    public function getActionCollector()
    {
        return $this->actionCollector;
    }

    /** {@inheritdoc} */
    public function visit($url)
    {
        $browser = parent::visit($url);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    /** {@inheritdoc} */
    public function visitRoute($route, $parameters = [])
    {
        $browser = parent::visitRoute($route, $parameters);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    /** {@inheritdoc} */
    public function refresh()
    {
        $browser = parent::refresh();

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    public function getCurrentPageSource()
    {
        $this->ensurejQueryIsAvailable();

        $this->restoreHtml();

        return $this->driver->executeScript('return document.documentElement.innerHTML;');
    }

    protected function restoreHtml()
    {
        $this->driver->executeScript("jQuery('input').attr('value', function() { return jQuery(this).val(); });");

        $this->driver->executeScript("jQuery('input[type=checkbox]').each(function() { jQuery(this).attr('checked', jQuery(this).prop(\"checked\")); });");

        $this->driver->executeScript("jQuery('textarea').each(function() { jQuery(this).html(jQuery(this).val()); });");

        $this->driver->executeScript("jQuery('input[type=radio]').each(function() { jQuery(this).attr('checked', this.checked); });");

        $this->driver->executeScript("jQuery('select option').each(function() { jQuery(this).attr('selected', this.selected); });");
    }
}


================================================
FILE: src/Dusk/Concerns/InteractsWithAuthentication.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

trait InteractsWithAuthentication
{
    /** {@inheritdoc} */
    public function login()
    {
        $browser = parent::login();

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    /** {@inheritdoc} */
    public function loginAs($userId, $guard = null)
    {
        $browser = parent::loginAs($userId, $guard);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    /** {@inheritdoc} */
    public function logout($guard = null)
    {
        $browser = parent::logout($guard);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    /** {@inheritdoc} */
    public function assertAuthenticated($guard = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertAuthenticated($guard);
    }

    /** {@inheritdoc} */
    public function assertGuest($guard = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertGuest($guard);
    }

    /** {@inheritdoc} */
    public function assertAuthenticatedAs($user, $guard = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertAuthenticatedAs($user, $guard);
    }
}


================================================
FILE: src/Dusk/Concerns/InteractsWithCookies.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

trait InteractsWithCookies
{
    /** {@inheritdoc} */
    public function cookie($name, $value = null, $expiry = null, array $options = [])
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::cookie($name, $value, $expiry, $options);
    }

    /** {@inheritdoc} */
    public function plainCookie($name, $value = null, $expiry = null, array $options = [])
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::plainCookie($name, $value, $expiry, $options);
    }

    /** {@inheritdoc} */
    public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::addCookie($name, $value, $expiry, $options, $encrypt);
    }

    /** {@inheritdoc} */
    public function deleteCookie($name)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::deleteCookie($name);
    }
}


================================================
FILE: src/Dusk/Concerns/InteractsWithElements.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

trait InteractsWithElements
{
    /** {@inheritdoc} */
    public function elements($selector)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::elements($selector);
    }

    /** {@inheritdoc} */
    public function element($selector)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::element($selector);
    }

    /** {@inheritdoc} */
    public function clickLink($link, $element = 'a')
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::clickLink($link, $element);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function value($selector, $value = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::value($selector, $value);
    }

    /** {@inheritdoc} */
    public function text($selector)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::text($selector);
    }

    /** {@inheritdoc} */
    public function attribute($selector, $attribute)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::attribute($selector, $attribute);
    }

    /** {@inheritdoc} */
    public function keys($selector, ...$keys)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::keys($selector, $keys);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function type($field, $value)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::type($field, $value);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function append($field, $value)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::append($field, $value);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function clear($field)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::clear($field);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function select($field, $value = null)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::select($field, $value);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function radio($field, $value)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::radio($field, $value);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function check($field, $value = null)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::check($field, $value);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function uncheck($field, $value = null)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::uncheck($field, $value);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function attach($field, $path)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::attach($field, $path);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function press($button)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::press($button);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function pressAndWaitFor($button, $seconds = 5)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::pressAndWaitFor($button, $seconds);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function drag($from, $to)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::drag($from, $to);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function dragUp($selector, $offset)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::dragUp($selector, $offset);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function dragDown($selector, $offset)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::dragDown($selector, $offset);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function dragLeft($selector, $offset)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::dragLeft($selector, $offset);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function dragRight($selector, $offset)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::dragRight($selector, $offset);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function dragOffset($selector, $x = 0, $y = 0)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::dragOffset($selector, $x, $y);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function acceptDialog()
    {
        $browser = parent::acceptDialog();

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    /** {@inheritdoc} */
    public function typeInDialog($value)
    {
        $browser = parent::typeInDialog($value);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }

    /** {@inheritdoc} */
    public function dismissDialog()
    {
        $browser = parent::dismissDialog();

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $browser;
    }
}


================================================
FILE: src/Dusk/Concerns/InteractsWithJavascript.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

trait InteractsWithJavascript
{
    /** {@inheritdoc} */
    public function script($scripts)
    {
        $result = parent::script($scripts);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return $result;
    }
}


================================================
FILE: src/Dusk/Concerns/InteractsWithMouse.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

trait InteractsWithMouse
{
    /** {@inheritdoc} */
    public function moveMouse($xOffset, $yOffset)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::moveMouse($xOffset, $yOffset);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function mouseover($selector)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::mouseover($selector);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function click($selector = null)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::click($selector);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function clickAndHold()
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::clickAndHold();

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    public function doubleClick()
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::doubleClick();

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function rightClick($selector = null)
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::rightClick($selector);

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }

    /** {@inheritdoc} */
    public function releaseMouse()
    {
        $previousHtml = $this->getCurrentPageSource();

        $browser = parent::releaseMouse();

        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);

        return $browser;
    }
}


================================================
FILE: src/Dusk/Concerns/MakesAssertions.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

trait MakesAssertions
{
    /** {@inheritdoc} */
    public function assertTitle($title)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertTitle($title);
    }

    /** {@inheritdoc} */
    public function assertTitleContains($title)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertTitleContains($title);
    }

    /** {@inheritdoc} */
    public function assertHasCookie($name, $decrypt = true)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertHasCookie($name, $decrypt);
    }

    /** {@inheritdoc} */
    public function assertHasPlainCookie($name)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertHasPlainCookie($name);
    }

    /** {@inheritdoc} */
    public function assertCookieMissing($name, $decrypt = true)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertCookieMissing($name, $decrypt);
    }

    /** {@inheritdoc} */
    public function assertPlainCookieMissing($name)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPlainCookieMissing($name);
    }

    /** {@inheritdoc} */
    public function assertCookieValue($name, $value, $decrypt = true)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertCookieValue($name, $value, $decrypt);
    }

    /** {@inheritdoc} */
    public function assertPlainCookieValue($name, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPlainCookieValue($name, $value);
    }

    /** {@inheritdoc} */
    public function assertSee($text)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSee($text);
    }

    /** {@inheritdoc} */
    public function assertSeeIn($selector, $text)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSeeIn($selector, $text);
    }

    /** {@inheritdoc} */
    public function assertDontSeeIn($selector, $text)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertDontSeeIn($selector, $text);
    }

    /** {@inheritdoc} */
    public function assertSourceHas($code)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSourceHas($code);
    }

    /** {@inheritdoc} */
    public function assertSourceMissing($code)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSourceMissing($code);
    }

    /** {@inheritdoc} */
    public function assertSeeLink($link)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSeeLink($link);
    }

    /** {@inheritdoc} */
    public function assertDontSeeLink($link)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertDontSeeLink($link);
    }

    /** {@inheritdoc} */
    public function seeLink($link)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::seeLink($link);
    }

    /** {@inheritdoc} */
    public function assertInputValue($field, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertInputValue($field, $value);
    }

    /** {@inheritdoc} */
    public function assertInputValueIsNot($field, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertInputValueIsNot($field, $value);
    }

    /** {@inheritdoc} */
    public function inputValue($field)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::inputValue($field);
    }

    /** {@inheritdoc} */
    public function assertChecked($field, $value = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertChecked($field, $value);
    }

    /** {@inheritdoc} */
    public function assertNotChecked($field, $value = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertNotChecked($field, $value);
    }

    /** {@inheritdoc} */
    public function assertRadioSelected($field, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertRadioSelected($field, $value);
    }

    /** {@inheritdoc} */
    public function assertRadioNotSelected($field, $value = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertRadioNotSelected($field, $value);
    }

    /** {@inheritdoc} */
    public function assertSelected($field, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSelectHasOption($field, $value);
    }

    /** {@inheritdoc} */
    public function assertNotSelected($field, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertNotSelected($field, $value);
    }

    /** {@inheritdoc} */
    public function assertSelectHasOptions($field, array $values)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSelectHasOptions($field, $values);
    }

    /** {@inheritdoc} */
    public function assertSelectMissingOptions($field, array $values)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSelectMissingOptions($field, $values);
    }

    /** {@inheritdoc} */
    public function assertSelectHasOption($field, $value)
    {
        return $this->assertSelectHasOptions($field, [$value]);
    }

    /** {@inheritdoc} */
    public function assertSelectMissingOption($field, $value)
    {
        return $this->assertSelectMissingOptions($field, [$value]);
    }

    /** {@inheritdoc} */
    public function selected($field, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::select($field, $value);
    }

    /** {@inheritdoc} */
    public function assertValue($selector, $value)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertValue($field, $value);
    }

    /** {@inheritdoc} */
    public function assertVisible($selector)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertVisible($selector);
    }

    /** {@inheritdoc} */
    public function assertPresent($selector)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPresent($selector);
    }

    /** {@inheritdoc} */
    public function assertMissing($selector)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertMissing($selector);
    }

    /** {@inheritdoc} */
    public function assertDialogOpened($message)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertDialogOpened($message);
    }

    /** {@inheritdoc} */
    public function assertEnabled($field)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertEnabled($field);
    }

    /** {@inheritdoc} */
    public function assertDisabled($field)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertDisabled($field);
    }

    /** {@inheritdoc} */
    public function assertFocused($field)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertFocused($field);
    }

    /** {@inheritdoc} */
    public function assertNotFocused($field)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertNotFocused($field);
    }

    /** {@inheritdoc} */
    public function assertVue($key, $value, $componentSelector = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertVue($key, $value, $componentSelector);
    }

    /** {@inheritdoc} */
    public function assertVueIsNot($key, $value, $componentSelector = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertVueIsNot($key, $value, $componentSelector);
    }

    /** {@inheritdoc} */
    public function assertVueContains($key, $value, $componentSelector = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertVueContains($key, $value, $componentSelector);
    }

    /** {@inheritdoc} */
    public function assertVueDoesNotContain($key, $value, $componentSelector = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertVueDoesNotContain($key, $value, $componentSelector);
    }

    /** {@inheritdoc} */
    public function vueAttribute($componentSelector, $key)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::vueAttribute($componentSelector, $key);
    }
}


================================================
FILE: src/Dusk/Concerns/MakesUrlAssertions.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

trait MakesUrlAssertions
{
    /** {@inheritdoc} */
    public function assertUrlIs($url)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertUrlIs($url);
    }

    /** {@inheritdoc} */
    public function assertSchemeIs($scheme)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSchemeIs($scheme);
    }

    /** {@inheritdoc} */
    public function assertSchemeIsNot($scheme)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertSchemeIsNot($scheme);
    }

    /** {@inheritdoc} */
    public function assertHostIs($host)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertHostIs($host);
    }

    /** {@inheritdoc} */
    public function assertHostIsNot($host)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertHostIsNot($host);
    }

    /** {@inheritdoc} */
    public function assertPortIs($port)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPortIs($port);
    }

    /** {@inheritdoc} */
    public function assertPortIsNot($port)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPortIsNot($port);
    }

    /** {@inheritdoc} */
    public function assertPathIs($path)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPathIs($path);
    }

    /** {@inheritdoc} */
    public function assertPathBeginsWith($path)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPathBeginsWith($path);
    }

    /** {@inheritdoc} */
    public function assertPathIsNot($path)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertPathIsNot($path);
    }

    /** {@inheritdoc} */
    public function assertFragmentIs($fragment)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertFragmentIs($fragment);
    }

    /** {@inheritdoc} */
    public function assertFragmentBeginsWith($fragment)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertFragmentBeginsWith($fragment);
    }

    /** {@inheritdoc} */
    public function assertFragmentIsNot($fragment)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertFragmentIsNot($fragment);
    }

    /** {@inheritdoc} */
    public function assertRouteIs($route, $parameters = [])
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertRouteIs($route, $parameters);
    }

    /** {@inheritdoc} */
    public function assertQueryStringHas($name, $value = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertQueryStringHas($name, $value);
    }

    /** {@inheritdoc} */
    public function assertQueryStringMissing($name)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertQueryStringMissing($name);
    }

    /** {@inheritdoc} */
    protected function assertHasQueryStringParameter($name)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::assertHasQueryStringParameter($name);
    }
}


================================================
FILE: src/Dusk/Concerns/WaitsForElements.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Dusk\Concerns;

use Closure;

trait WaitsForElements
{
    /** {@inheritdoc} */
    public function whenAvailable($selector, Closure $callback, $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::whenAvailable($selector, $callback, $seconds);
    }

    /** {@inheritdoc} */
    public function waitFor($selector, $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitFor($selector, $seconds);
    }

    /** {@inheritdoc} */
    public function waitUntilMissing($selector, $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitUntilMissing($selector, $seconds);
    }

    /** {@inheritdoc} */
    public function waitForText($text, $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitForText($text, $seconds);
    }

    /** {@inheritdoc} */
    public function waitForLink($link, $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitForLink($link, $seconds);
    }

    /** {@inheritdoc} */
    public function waitForLocation($path, $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitForLocation($path, $seconds);
    }

    /** {@inheritdoc} */
    public function waitForRoute($route, $parameters = [], $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitForRoute($route, $parameters, $seconds);
    }

    /** {@inheritdoc} */
    public function waitUntil($script, $seconds = null, $message = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitUntil($script, $seconds, $message);
    }

    /** {@inheritdoc} */
    public function waitForDialog($seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitForDialog($seconds);
    }

    /** {@inheritdoc} */
    public function waitForReload($callback = null, $seconds = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitForReload($callback, $seconds);
    }

    /** {@inheritdoc} */
    public function waitUsing($seconds, $interval, Closure $callback, $message = null)
    {
        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);

        return parent::waitUsing($seconds, $interval, $callback, $message);
    }
}


================================================
FILE: src/DuskDashboardServiceProvider.php
================================================
<?php

namespace BeyondCode\DuskDashboard;

use Illuminate\Support\ServiceProvider;

class DuskDashboardServiceProvider extends ServiceProvider
{
    /**
     * Register the application services.
     */
    public function register()
    {
        $this->commands([
            Console\StartDashboardCommand::class,
        ]);
    }
}


================================================
FILE: src/DuskProcessFactory.php
================================================
<?php

namespace BeyondCode\DuskDashboard;

use Symfony\Component\Process\Process;

class DuskProcessFactory
{
    /**
     * Create new process to run Dusk, passing the same arguments Dashboard command received.
     *
     * @return \Symfony\Component\Process\Process
     */
    public static function make()
    {
        return new Process(array_merge(self::binary(), self::arguments()), base_path());
    }

    /**
     * Dusk command represented as array.
     *
     * @return array
     */
    protected static function binary()
    {
        return [PHP_BINARY, 'artisan', 'dusk'];
    }

    /**
     * Arguments that were given to dusk:dashboard.
     *
     * @return array
     */
    protected static function arguments()
    {
        return array_slice($_SERVER['argv'], 2);
    }
}


================================================
FILE: src/Ratchet/Http/Controller.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Ratchet\Http;

use Exception;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServerInterface;

abstract class Controller implements HttpServerInterface
{
    public function onClose(ConnectionInterface $connection)
    {
    }

    public function onError(ConnectionInterface $connection, Exception $e)
    {
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
    }
}


================================================
FILE: src/Ratchet/Http/DashboardController.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Ratchet\Http;

use GuzzleHttp\Psr7\Response;
use function GuzzleHttp\Psr7\str;
use Psr\Http\Message\RequestInterface;
use Ratchet\ConnectionInterface;

class DashboardController extends Controller
{
    public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)
    {
        $connection->send(
            str(new Response(
                200,
                ['Content-Type' => 'text/html'],
                file_get_contents(__DIR__.'/../../../resources/views/index.html')
            ))
        );

        $connection->close();
    }
}


================================================
FILE: src/Ratchet/Http/EventController.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Ratchet\Http;

use BeyondCode\DuskDashboard\Ratchet\Socket;
use Exception;
use GuzzleHttp\Psr7\Response;
use function GuzzleHttp\Psr7\str;
use Psr\Http\Message\RequestInterface;
use Ratchet\ConnectionInterface;

class EventController extends Controller
{
    public function onOpen(ConnectionInterface $conn, RequestInterface $request = null)
    {
        try {

            /*
             * This is the post payload from our PHPUnit tests.
             * Send it to the connected connections.
             */
            foreach (Socket::$connections as $connection) {
                $connection->send($request->getBody());
            }

            $conn->send(str(new Response(200)));
        } catch (Exception $e) {
            $conn->send(str(new Response(500, [], $e->getMessage())));
        }

        $conn->close();
    }
}


================================================
FILE: src/Ratchet/Server/App.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Ratchet\Server;

use Ratchet\Http\Router;
use Ratchet\Server\IoServer;
use React\EventLoop\LoopInterface;
use React\Socket\Server as Reactor;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;

class App extends \Ratchet\App
{
    public function __construct($httpHost, $port, $address, LoopInterface $loop)
    {
        $this->httpHost = $httpHost;
        $this->port = $port;

        $socket = new Reactor($address.':'.$port, $loop);

        $this->routes = new RouteCollection;

        $urlMatcher = new UrlMatcher($this->routes, new RequestContext);

        $router = new Router($urlMatcher);

        $httpServer = new HttpServer($router);

        $this->_server = new IoServer($httpServer, $socket, $loop);
    }
}


================================================
FILE: src/Ratchet/Server/HttpServer.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Ratchet\Server;

use Ratchet\Http\HttpServerInterface;

class HttpServer extends \Ratchet\Http\HttpServer
{
    public function __construct(HttpServerInterface $component)
    {
        parent::__construct($component);

        $this->_reqParser->maxSize = 5242880;
    }
}


================================================
FILE: src/Ratchet/Socket.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Ratchet;

use BeyondCode\DuskDashboard\DuskProcessFactory;
use Ratchet\ConnectionInterface;
use Ratchet\RFC6455\Messaging\MessageInterface;
use Ratchet\WebSocket\MessageComponentInterface;

class Socket implements MessageComponentInterface
{
    public static $connections = [];

    public function onOpen(ConnectionInterface $connection)
    {
        self::$connections[] = $connection;
    }

    public function onMessage(ConnectionInterface $from, MessageInterface $msg)
    {
        $data = json_decode($msg);

        if ($data->method === 'startTests') {
            $process = DuskProcessFactory::make();

            $process->start();
        }
    }

    public function onClose(ConnectionInterface $connection)
    {
    }

    public function onError(ConnectionInterface $connection, \Exception $e)
    {
    }
}


================================================
FILE: src/Testing/TestCase.php
================================================
<?php

namespace BeyondCode\DuskDashboard\Testing;

use BeyondCode\DuskDashboard\BrowserActionCollector;
use BeyondCode\DuskDashboard\Console\StartDashboardCommand;
use BeyondCode\DuskDashboard\Dusk\Browser;
use Closure;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use Laravel\Dusk\TestCase as BaseTestCase;
use Throwable;

abstract class TestCase extends BaseTestCase
{
    /**
     * Create a new Browser instance.
     *
     * @param  \Facebook\WebDriver\Remote\RemoteWebDriver  $driver
     * @return \BeyondCode\DuskDashboard\Dusk\Browser
     */
    protected function newBrowser($driver)
    {
        return new Browser($driver);
    }

    /**
     * Create the browser instances needed for the given callback.
     *
     * @param  \Closure  $callback
     * @return array
     * @throws \ReflectionException
     */
    protected function createBrowsersFor(Closure $callback)
    {
        $browsers = parent::createBrowsersFor($callback);

        foreach ($browsers as $browser) {
            $browser->setActionCollector(new BrowserActionCollector($this->getTestName()));
        }

        static::$browsers = $browsers;

        return static::$browsers;
    }

    protected function getTestName()
    {
        return class_basename(static::class).'::'.$this->getName();
    }

    protected function enableNetworkLogging(DesiredCapabilities $capabilities): DesiredCapabilities
    {
        $chromeOptions = $capabilities->getCapability(ChromeOptions::CAPABILITY);

        $perfLoggingPrefs = new \stdClass();
        $perfLoggingPrefs->enableNetwork = true;

        $chromeOptions->setExperimentalOption('perfLoggingPrefs', $perfLoggingPrefs);

        $capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions);

        $loggingPrefs = new \stdClass();
        $loggingPrefs->browser = 'ALL';
        $loggingPrefs->performance = 'ALL';

        $capabilities->setCapability('loggingPrefs', $loggingPrefs);

        return $capabilities;
    }

    protected function onNotSuccessfulTest(Throwable $t): void
    {
        try {
            (new Client())->post('http://127.0.0.1:'.StartDashboardCommand::PORT.'/events', [
                RequestOptions::JSON => [
                    'channel' => 'dusk-dashboard',
                    'name' => 'dusk-failure',
                    'data' => [
                        'message' => $t->getMessage(),
                    ],
                ],
            ]);
        } catch (\Exception $e) {
            // Dashboard is offline
        } finally {
            parent::onNotSuccessfulTest($t);
        }
    }
}


================================================
FILE: src/Watcher.php
================================================
<?php

namespace BeyondCode\DuskDashboard;

use Closure;
use React\EventLoop\LoopInterface;
use Symfony\Component\Finder\Finder;
use Yosymfony\ResourceWatcher\Crc32ContentHash;
use Yosymfony\ResourceWatcher\ResourceCacheMemory;
use Yosymfony\ResourceWatcher\ResourceWatcher;

class Watcher
{
    /** @var \Symfony\Component\Finder\Finder */
    protected $finder;

    /** @var \React\EventLoop\LoopInterface */
    protected $loop;

    public function __construct(Finder $finder, LoopInterface $loop)
    {
        $this->finder = $finder;
        $this->loop = $loop;
    }

    public function startWatching(Closure $callback)
    {
        $hashContent = new Crc32ContentHash();
        $watcher = new ResourceWatcher(new ResourceCacheMemory(), $this->finder, $hashContent);

        $this->loop->addPeriodicTimer(1 / 2, function () use ($watcher, $callback) {
            $watcher_result = $watcher->findChanges();

            if ($watcher_result->hasChanges()) {
                call_user_func($callback);
            }
        });
    }
}
Download .txt
gitextract_fazur9k6/

├── .editorconfig
├── .gitattributes
├── .gitignore
├── .scrutinizer.yml
├── .styleci.yml
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config/
│   └── dusk-dashboard.php
├── phpunit.xml.dist
├── resources/
│   └── views/
│       └── index.html
└── src/
    ├── Action.php
    ├── BrowserActionCollector.php
    ├── Console/
    │   └── StartDashboardCommand.php
    ├── Dusk/
    │   ├── Browser.php
    │   └── Concerns/
    │       ├── InteractsWithAuthentication.php
    │       ├── InteractsWithCookies.php
    │       ├── InteractsWithElements.php
    │       ├── InteractsWithJavascript.php
    │       ├── InteractsWithMouse.php
    │       ├── MakesAssertions.php
    │       ├── MakesUrlAssertions.php
    │       └── WaitsForElements.php
    ├── DuskDashboardServiceProvider.php
    ├── DuskProcessFactory.php
    ├── Ratchet/
    │   ├── Http/
    │   │   ├── Controller.php
    │   │   ├── DashboardController.php
    │   │   └── EventController.php
    │   ├── Server/
    │   │   ├── App.php
    │   │   └── HttpServer.php
    │   └── Socket.php
    ├── Testing/
    │   └── TestCase.php
    └── Watcher.php
Download .txt
SYMBOL INDEX (186 symbols across 22 files)

FILE: src/Action.php
  class Action (line 5) | class Action
    method __construct (line 17) | public function __construct(string $name, array $arguments, string $ht...
    method getName (line 28) | public function getName()
    method getHtml (line 33) | public function getHtml()
    method setPreviousHtml (line 38) | public function setPreviousHtml(?string $html)
    method getPreviousHtml (line 43) | public function getPreviousHtml()
    method getArguments (line 48) | public function getArguments()
    method getPath (line 53) | public function getPath()

FILE: src/BrowserActionCollector.php
  class BrowserActionCollector (line 10) | class BrowserActionCollector
    method __construct (line 17) | public function __construct($testName)
    method collect (line 24) | public function collect(string $action, array $arguments, Browser $bro...
    method processPerformanceLog (line 44) | protected function processPerformanceLog(Browser $browser)
    method pushAction (line 80) | protected function pushAction(string $name, array $payload)
    method pushPerformanceLog (line 95) | protected function pushPerformanceLog(array $log, Browser $browser)

FILE: src/Console/StartDashboardCommand.php
  class StartDashboardCommand (line 19) | class StartDashboardCommand extends Command
    method __construct (line 32) | public function __construct()
    method handle (line 45) | public function handle()
    method addRoutes (line 68) | protected function addRoutes()
    method createTestWatcher (line 79) | protected function createTestWatcher()
    method getTestSuitePath (line 104) | protected function getTestSuitePath()
    method createApp (line 121) | protected function createApp(array $url)
    method tryToOpenInBrowser (line 134) | protected function tryToOpenInBrowser($url)

FILE: src/Dusk/Browser.php
  class Browser (line 7) | class Browser extends \Laravel\Dusk\Browser
    method setActionCollector (line 21) | public function setActionCollector(BrowserActionCollector $collector)
    method getActionCollector (line 29) | public function getActionCollector()
    method visit (line 35) | public function visit($url)
    method visitRoute (line 45) | public function visitRoute($route, $parameters = [])
    method refresh (line 55) | public function refresh()
    method getCurrentPageSource (line 64) | public function getCurrentPageSource()
    method restoreHtml (line 73) | protected function restoreHtml()

FILE: src/Dusk/Concerns/InteractsWithAuthentication.php
  type InteractsWithAuthentication (line 5) | trait InteractsWithAuthentication
    method login (line 8) | public function login()
    method loginAs (line 18) | public function loginAs($userId, $guard = null)
    method logout (line 28) | public function logout($guard = null)
    method assertAuthenticated (line 38) | public function assertAuthenticated($guard = null)
    method assertGuest (line 46) | public function assertGuest($guard = null)
    method assertAuthenticatedAs (line 54) | public function assertAuthenticatedAs($user, $guard = null)

FILE: src/Dusk/Concerns/InteractsWithCookies.php
  type InteractsWithCookies (line 5) | trait InteractsWithCookies
    method cookie (line 8) | public function cookie($name, $value = null, $expiry = null, array $op...
    method plainCookie (line 16) | public function plainCookie($name, $value = null, $expiry = null, arra...
    method addCookie (line 24) | public function addCookie($name, $value, $expiry = null, array $option...
    method deleteCookie (line 32) | public function deleteCookie($name)

FILE: src/Dusk/Concerns/InteractsWithElements.php
  type InteractsWithElements (line 5) | trait InteractsWithElements
    method elements (line 8) | public function elements($selector)
    method element (line 16) | public function element($selector)
    method clickLink (line 24) | public function clickLink($link, $element = 'a')
    method value (line 36) | public function value($selector, $value = null)
    method text (line 44) | public function text($selector)
    method attribute (line 52) | public function attribute($selector, $attribute)
    method keys (line 60) | public function keys($selector, ...$keys)
    method type (line 72) | public function type($field, $value)
    method append (line 84) | public function append($field, $value)
    method clear (line 96) | public function clear($field)
    method select (line 108) | public function select($field, $value = null)
    method radio (line 120) | public function radio($field, $value)
    method check (line 132) | public function check($field, $value = null)
    method uncheck (line 144) | public function uncheck($field, $value = null)
    method attach (line 156) | public function attach($field, $path)
    method press (line 168) | public function press($button)
    method pressAndWaitFor (line 180) | public function pressAndWaitFor($button, $seconds = 5)
    method drag (line 192) | public function drag($from, $to)
    method dragUp (line 204) | public function dragUp($selector, $offset)
    method dragDown (line 216) | public function dragDown($selector, $offset)
    method dragLeft (line 228) | public function dragLeft($selector, $offset)
    method dragRight (line 240) | public function dragRight($selector, $offset)
    method dragOffset (line 252) | public function dragOffset($selector, $x = 0, $y = 0)
    method acceptDialog (line 264) | public function acceptDialog()
    method typeInDialog (line 274) | public function typeInDialog($value)
    method dismissDialog (line 284) | public function dismissDialog()

FILE: src/Dusk/Concerns/InteractsWithJavascript.php
  type InteractsWithJavascript (line 5) | trait InteractsWithJavascript
    method script (line 8) | public function script($scripts)

FILE: src/Dusk/Concerns/InteractsWithMouse.php
  type InteractsWithMouse (line 5) | trait InteractsWithMouse
    method moveMouse (line 8) | public function moveMouse($xOffset, $yOffset)
    method mouseover (line 20) | public function mouseover($selector)
    method click (line 32) | public function click($selector = null)
    method clickAndHold (line 44) | public function clickAndHold()
    method doubleClick (line 55) | public function doubleClick()
    method rightClick (line 67) | public function rightClick($selector = null)
    method releaseMouse (line 79) | public function releaseMouse()

FILE: src/Dusk/Concerns/MakesAssertions.php
  type MakesAssertions (line 5) | trait MakesAssertions
    method assertTitle (line 8) | public function assertTitle($title)
    method assertTitleContains (line 16) | public function assertTitleContains($title)
    method assertHasCookie (line 24) | public function assertHasCookie($name, $decrypt = true)
    method assertHasPlainCookie (line 32) | public function assertHasPlainCookie($name)
    method assertCookieMissing (line 40) | public function assertCookieMissing($name, $decrypt = true)
    method assertPlainCookieMissing (line 48) | public function assertPlainCookieMissing($name)
    method assertCookieValue (line 56) | public function assertCookieValue($name, $value, $decrypt = true)
    method assertPlainCookieValue (line 64) | public function assertPlainCookieValue($name, $value)
    method assertSee (line 72) | public function assertSee($text)
    method assertSeeIn (line 80) | public function assertSeeIn($selector, $text)
    method assertDontSeeIn (line 88) | public function assertDontSeeIn($selector, $text)
    method assertSourceHas (line 96) | public function assertSourceHas($code)
    method assertSourceMissing (line 104) | public function assertSourceMissing($code)
    method assertSeeLink (line 112) | public function assertSeeLink($link)
    method assertDontSeeLink (line 120) | public function assertDontSeeLink($link)
    method seeLink (line 128) | public function seeLink($link)
    method assertInputValue (line 136) | public function assertInputValue($field, $value)
    method assertInputValueIsNot (line 144) | public function assertInputValueIsNot($field, $value)
    method inputValue (line 152) | public function inputValue($field)
    method assertChecked (line 160) | public function assertChecked($field, $value = null)
    method assertNotChecked (line 168) | public function assertNotChecked($field, $value = null)
    method assertRadioSelected (line 176) | public function assertRadioSelected($field, $value)
    method assertRadioNotSelected (line 184) | public function assertRadioNotSelected($field, $value = null)
    method assertSelected (line 192) | public function assertSelected($field, $value)
    method assertNotSelected (line 200) | public function assertNotSelected($field, $value)
    method assertSelectHasOptions (line 208) | public function assertSelectHasOptions($field, array $values)
    method assertSelectMissingOptions (line 216) | public function assertSelectMissingOptions($field, array $values)
    method assertSelectHasOption (line 224) | public function assertSelectHasOption($field, $value)
    method assertSelectMissingOption (line 230) | public function assertSelectMissingOption($field, $value)
    method selected (line 236) | public function selected($field, $value)
    method assertValue (line 244) | public function assertValue($selector, $value)
    method assertVisible (line 252) | public function assertVisible($selector)
    method assertPresent (line 260) | public function assertPresent($selector)
    method assertMissing (line 268) | public function assertMissing($selector)
    method assertDialogOpened (line 276) | public function assertDialogOpened($message)
    method assertEnabled (line 284) | public function assertEnabled($field)
    method assertDisabled (line 292) | public function assertDisabled($field)
    method assertFocused (line 300) | public function assertFocused($field)
    method assertNotFocused (line 308) | public function assertNotFocused($field)
    method assertVue (line 316) | public function assertVue($key, $value, $componentSelector = null)
    method assertVueIsNot (line 324) | public function assertVueIsNot($key, $value, $componentSelector = null)
    method assertVueContains (line 332) | public function assertVueContains($key, $value, $componentSelector = n...
    method assertVueDoesNotContain (line 340) | public function assertVueDoesNotContain($key, $value, $componentSelect...
    method vueAttribute (line 348) | public function vueAttribute($componentSelector, $key)

FILE: src/Dusk/Concerns/MakesUrlAssertions.php
  type MakesUrlAssertions (line 5) | trait MakesUrlAssertions
    method assertUrlIs (line 8) | public function assertUrlIs($url)
    method assertSchemeIs (line 16) | public function assertSchemeIs($scheme)
    method assertSchemeIsNot (line 24) | public function assertSchemeIsNot($scheme)
    method assertHostIs (line 32) | public function assertHostIs($host)
    method assertHostIsNot (line 40) | public function assertHostIsNot($host)
    method assertPortIs (line 48) | public function assertPortIs($port)
    method assertPortIsNot (line 56) | public function assertPortIsNot($port)
    method assertPathIs (line 64) | public function assertPathIs($path)
    method assertPathBeginsWith (line 72) | public function assertPathBeginsWith($path)
    method assertPathIsNot (line 80) | public function assertPathIsNot($path)
    method assertFragmentIs (line 88) | public function assertFragmentIs($fragment)
    method assertFragmentBeginsWith (line 96) | public function assertFragmentBeginsWith($fragment)
    method assertFragmentIsNot (line 104) | public function assertFragmentIsNot($fragment)
    method assertRouteIs (line 112) | public function assertRouteIs($route, $parameters = [])
    method assertQueryStringHas (line 120) | public function assertQueryStringHas($name, $value = null)
    method assertQueryStringMissing (line 128) | public function assertQueryStringMissing($name)
    method assertHasQueryStringParameter (line 136) | protected function assertHasQueryStringParameter($name)

FILE: src/Dusk/Concerns/WaitsForElements.php
  type WaitsForElements (line 7) | trait WaitsForElements
    method whenAvailable (line 10) | public function whenAvailable($selector, Closure $callback, $seconds =...
    method waitFor (line 18) | public function waitFor($selector, $seconds = null)
    method waitUntilMissing (line 26) | public function waitUntilMissing($selector, $seconds = null)
    method waitForText (line 34) | public function waitForText($text, $seconds = null)
    method waitForLink (line 42) | public function waitForLink($link, $seconds = null)
    method waitForLocation (line 50) | public function waitForLocation($path, $seconds = null)
    method waitForRoute (line 58) | public function waitForRoute($route, $parameters = [], $seconds = null)
    method waitUntil (line 66) | public function waitUntil($script, $seconds = null, $message = null)
    method waitForDialog (line 74) | public function waitForDialog($seconds = null)
    method waitForReload (line 82) | public function waitForReload($callback = null, $seconds = null)
    method waitUsing (line 90) | public function waitUsing($seconds, $interval, Closure $callback, $mes...

FILE: src/DuskDashboardServiceProvider.php
  class DuskDashboardServiceProvider (line 7) | class DuskDashboardServiceProvider extends ServiceProvider
    method register (line 12) | public function register()

FILE: src/DuskProcessFactory.php
  class DuskProcessFactory (line 7) | class DuskProcessFactory
    method make (line 14) | public static function make()
    method binary (line 24) | protected static function binary()
    method arguments (line 34) | protected static function arguments()

FILE: src/Ratchet/Http/Controller.php
  class Controller (line 9) | abstract class Controller implements HttpServerInterface
    method onClose (line 11) | public function onClose(ConnectionInterface $connection)
    method onError (line 15) | public function onError(ConnectionInterface $connection, Exception $e)
    method onMessage (line 19) | public function onMessage(ConnectionInterface $from, $msg)

FILE: src/Ratchet/Http/DashboardController.php
  class DashboardController (line 10) | class DashboardController extends Controller
    method onOpen (line 12) | public function onOpen(ConnectionInterface $connection, RequestInterfa...

FILE: src/Ratchet/Http/EventController.php
  class EventController (line 12) | class EventController extends Controller
    method onOpen (line 14) | public function onOpen(ConnectionInterface $conn, RequestInterface $re...

FILE: src/Ratchet/Server/App.php
  class App (line 13) | class App extends \Ratchet\App
    method __construct (line 15) | public function __construct($httpHost, $port, $address, LoopInterface ...

FILE: src/Ratchet/Server/HttpServer.php
  class HttpServer (line 7) | class HttpServer extends \Ratchet\Http\HttpServer
    method __construct (line 9) | public function __construct(HttpServerInterface $component)

FILE: src/Ratchet/Socket.php
  class Socket (line 10) | class Socket implements MessageComponentInterface
    method onOpen (line 14) | public function onOpen(ConnectionInterface $connection)
    method onMessage (line 19) | public function onMessage(ConnectionInterface $from, MessageInterface ...
    method onClose (line 30) | public function onClose(ConnectionInterface $connection)
    method onError (line 34) | public function onError(ConnectionInterface $connection, \Exception $e)

FILE: src/Testing/TestCase.php
  class TestCase (line 16) | abstract class TestCase extends BaseTestCase
    method newBrowser (line 24) | protected function newBrowser($driver)
    method createBrowsersFor (line 36) | protected function createBrowsersFor(Closure $callback)
    method getTestName (line 49) | protected function getTestName()
    method enableNetworkLogging (line 54) | protected function enableNetworkLogging(DesiredCapabilities $capabilit...
    method onNotSuccessfulTest (line 74) | protected function onNotSuccessfulTest(Throwable $t): void

FILE: src/Watcher.php
  class Watcher (line 12) | class Watcher
    method __construct (line 20) | public function __construct(Finder $finder, LoopInterface $loop)
    method startWatching (line 26) | public function startWatching(Closure $callback)
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (79K chars).
[
  {
    "path": ".editorconfig",
    "chars": 312,
    "preview": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.o"
  },
  {
    "path": ".gitattributes",
    "chars": 395,
    "preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
  },
  {
    "path": ".gitignore",
    "chars": 40,
    "preview": "build\ncomposer.lock\ndocs\nvendor\ncoverage"
  },
  {
    "path": ".scrutinizer.yml",
    "chars": 507,
    "preview": "filter:\n    excluded_paths: [tests/*]\n\nchecks:\n    php:\n        remove_extra_empty_lines: true\n        remove_php_closin"
  },
  {
    "path": ".styleci.yml",
    "chars": 66,
    "preview": "preset: laravel\n\ndisabled:\n  - single_class_element_per_statement\n"
  },
  {
    "path": ".travis.yml",
    "chars": 441,
    "preview": "language: php\n\nphp:\n  - 7.1\n  - 7.2\n  - 7.3\n\nenv:\n  matrix:\n    - COMPOSER_FLAGS=\"--prefer-lowest\"\n    - COMPOSER_FLAGS="
  },
  {
    "path": "CHANGELOG.md",
    "chars": 127,
    "preview": "# Changelog\n\nAll notable changes to `dusk-dashboard` will be documented in this file\n\n## 1.0.0 - 201X-XX-XX\n\n- initial r"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2972,
    "preview": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contributi"
  },
  {
    "path": "LICENSE.md",
    "chars": 1098,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Beyond Code GmbH <hello@beyondco.de>\n\nPermission is hereby granted, free of charge,"
  },
  {
    "path": "README.md",
    "chars": 2063,
    "preview": "# Laravel Dusk Dashboard\n\nA beautiful dashboard for your Dusk test suites.\n\n[![Latest Version on Packagist](https://img."
  },
  {
    "path": "composer.json",
    "chars": 1516,
    "preview": "{\n    \"name\": \"beyondcode/dusk-dashboard\",\n    \"description\": \"A beautiful dashboard for your Laravel Dusk tests\",\n    \""
  },
  {
    "path": "config/dusk-dashboard.php",
    "chars": 203,
    "preview": "<?php\n\nreturn [\n    /*\n     * You may specify which host the dashboard runs on in case it differs from the APP_URL\n     "
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1050,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         "
  },
  {
    "path": "resources/views/index.html",
    "chars": 14813,
    "preview": "<!doctype html>\n<html class=\"h-full\">\n<head>\n    <!-- Meta Information -->\n    <meta charset=\"utf-8\">\n    <meta name=\"vi"
  },
  {
    "path": "src/Action.php",
    "chars": 899,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nclass Action\n{\n    protected $name;\n\n    protected $arguments;\n\n    protecte"
  },
  {
    "path": "src/BrowserActionCollector.php",
    "chars": 3608,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse BeyondCode\\DuskDashboard\\Console\\StartDashboardCommand;\nuse BeyondCode\\D"
  },
  {
    "path": "src/Console/StartDashboardCommand.php",
    "chars": 3771,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Console;\n\nuse BeyondCode\\DuskDashboard\\DuskProcessFactory;\nuse BeyondCode\\Dusk"
  },
  {
    "path": "src/Dusk/Browser.php",
    "chars": 2441,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk;\n\nuse BeyondCode\\DuskDashboard\\BrowserActionCollector;\n\nclass Browser ext"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithAuthentication.php",
    "chars": 1458,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithAuthentication\n{\n    /** {@inheritdoc} */\n "
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithCookies.php",
    "chars": 1133,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithCookies\n{\n    /** {@inheritdoc} */\n    publ"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithElements.php",
    "chars": 7454,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithElements\n{\n    /** {@inheritdoc} */\n    pub"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithJavascript.php",
    "chars": 315,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithJavascript\n{\n    /** {@inheritdoc} */\n    p"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithMouse.php",
    "chars": 2184,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithMouse\n{\n    /** {@inheritdoc} */\n    public"
  },
  {
    "path": "src/Dusk/Concerns/MakesAssertions.php",
    "chars": 9943,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait MakesAssertions\n{\n    /** {@inheritdoc} */\n    public fu"
  },
  {
    "path": "src/Dusk/Concerns/MakesUrlAssertions.php",
    "chars": 3777,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait MakesUrlAssertions\n{\n    /** {@inheritdoc} */\n    public"
  },
  {
    "path": "src/Dusk/Concerns/WaitsForElements.php",
    "chars": 2788,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\nuse Closure;\n\ntrait WaitsForElements\n{\n    /** {@inheritdoc} *"
  },
  {
    "path": "src/DuskDashboardServiceProvider.php",
    "chars": 337,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse Illuminate\\Support\\ServiceProvider;\n\nclass DuskDashboardServiceProvider "
  },
  {
    "path": "src/DuskProcessFactory.php",
    "chars": 801,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse Symfony\\Component\\Process\\Process;\n\nclass DuskProcessFactory\n{\n    /**\n "
  },
  {
    "path": "src/Ratchet/Http/Controller.php",
    "chars": 442,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Http;\n\nuse Exception;\nuse Ratchet\\ConnectionInterface;\nuse Ratchet\\Htt"
  },
  {
    "path": "src/Ratchet/Http/DashboardController.php",
    "chars": 615,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Http;\n\nuse GuzzleHttp\\Psr7\\Response;\nuse function GuzzleHttp\\Psr7\\str;"
  },
  {
    "path": "src/Ratchet/Http/EventController.php",
    "chars": 881,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Http;\n\nuse BeyondCode\\DuskDashboard\\Ratchet\\Socket;\nuse Exception;\nuse"
  },
  {
    "path": "src/Ratchet/Server/App.php",
    "chars": 862,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Server;\n\nuse Ratchet\\Http\\Router;\nuse Ratchet\\Server\\IoServer;\nuse Rea"
  },
  {
    "path": "src/Ratchet/Server/HttpServer.php",
    "chars": 316,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Server;\n\nuse Ratchet\\Http\\HttpServerInterface;\n\nclass HttpServer exten"
  },
  {
    "path": "src/Ratchet/Socket.php",
    "chars": 871,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet;\n\nuse BeyondCode\\DuskDashboard\\DuskProcessFactory;\nuse Ratchet\\Connect"
  },
  {
    "path": "src/Testing/TestCase.php",
    "chars": 2703,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Testing;\n\nuse BeyondCode\\DuskDashboard\\BrowserActionCollector;\nuse BeyondCode\\"
  },
  {
    "path": "src/Watcher.php",
    "chars": 1048,
    "preview": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse Closure;\nuse React\\EventLoop\\LoopInterface;\nuse Symfony\\Component\\Finder"
  }
]

About this extraction

This page contains the full source code of the beyondcode/dusk-dashboard GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (72.5 KB), approximately 17.7k tokens, and a symbol index with 186 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!