[
  {
    "path": ".editorconfig",
    "content": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and documentation with \"export-ignore\".\n/.gitattributes     export-ignore\n/.gitignore         export-ignore\n/.travis.yml        export-ignore\n/phpunit.xml.dist   export-ignore\n/.scrutinizer.yml   export-ignore\n/tests              export-ignore\n/.editorconfig      export-ignore\n"
  },
  {
    "path": ".gitignore",
    "content": "build\ncomposer.lock\ndocs\nvendor\ncoverage"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "filter:\n    excluded_paths: [tests/*]\n\nchecks:\n    php:\n        remove_extra_empty_lines: true\n        remove_php_closing_tag: true\n        remove_trailing_whitespace: true\n        fix_use_statements:\n            remove_unused: true\n            preserve_multiple: false\n            preserve_blanklines: true\n            order_alphabetically: true\n        fix_php_opening_tag: true\n        fix_linefeed: true\n        fix_line_ending: true\n        fix_identation_4spaces: true\n        fix_doc_comments: true\n\n"
  },
  {
    "path": ".styleci.yml",
    "content": "preset: laravel\n\ndisabled:\n  - single_class_element_per_statement\n"
  },
  {
    "path": ".travis.yml",
    "content": "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=\"\"\n\nbefore_script:\n  - travis_retry composer self-update\n  - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source\n\nscript:\n  - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover\n\nafter_script:\n  - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 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 release\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contribution guide before creating an issue or pull request.\n\n## Etiquette\n\nThis project is open source, and as such, the maintainers give their free time to build and maintain the source code\nheld within. They make the code freely available in the hope that it will be of use to other developers. It would be\nextremely unfair for them to suffer abuse or anger for their hard work.\n\nPlease be considerate towards maintainers when raising issues or presenting pull requests. Let's show the\nworld that developers are civilized and selfless people.\n\nIt's the duty of the maintainer to ensure that all submissions to the project are of sufficient\nquality 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.\n\n## Viability\n\nWhen requesting or submitting new features, first consider whether it might be useful to others. Open\nsource projects are used by many developers, who may have entirely different needs to your own. Think about\nwhether or not your feature is likely to be used by other users of the project.\n\n## Procedure\n\nBefore filing an issue:\n\n- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.\n- Check to make sure your feature suggestion isn't already present within the project.\n- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.\n- Check the pull requests tab to ensure that the feature isn't already in progress.\n\nBefore submitting a pull request:\n\n- Check the codebase to ensure that your feature doesn't already exist.\n- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.\n\n## Requirements\n\nIf the project maintainer has any additional requirements, you will find them listed here.\n\n- **[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).\n\n- **Add tests!** - Your patch won't be accepted if it doesn't have tests.\n\n- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.\n\n- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.\n\n- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.\n\n- **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.\n\n**Happy coding**!\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) Beyond Code GmbH <hello@beyondco.de>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Laravel Dusk Dashboard\n\nA beautiful dashboard for your Dusk test suites.\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/beyondcode/dusk-dashboard.svg?style=flat-square)](https://packagist.org/packages/beyondcode/dusk-dashboard)\n[![Quality Score](https://img.shields.io/scrutinizer/g/beyondcode/dusk-dashboard.svg?style=flat-square)](https://scrutinizer-ci.com/g/beyondcode/dusk-dashboard)\n[![Total Downloads](https://img.shields.io/packagist/dt/beyondcode/dusk-dashboard.svg?style=flat-square)](https://packagist.org/packages/beyondcode/dusk-dashboard)\n\n![](https://pociot.dev/storage/22/dusk-dashboard.gif)\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require beyondcode/dusk-dashboard --dev\n```\n\nNext up, you need to go to your `DuskTestCase.php` that was installed by Laravel Dusk. You can find this file in your `tests` directory:\n\nFind and replace this line:\n```php\nuse Laravel\\Dusk\\TestCase as BaseTestCase;\n```\nwith:\n```php\nuse BeyondCode\\DuskDashboard\\Testing\\TestCase as BaseTestCase;\n```\n\n## Usage\n\n```\nphp artisan dusk:dashboard\n```\n\nCheck out the documentation [here](https://pociot.dev/8-introducing-laravel-dusk-dashboard).\n\n### Handling Asset Paths\n\nAssets 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.\n\n### Testing\n\n``` bash\ncomposer test\n```\n\n### Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n### Security\n\nIf you discover any security related issues, please email marcel@beyondco.de instead of using the issue tracker.\n\n## Credits\n\n- [Marcel Pociot](https://github.com/mpociot)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"beyondcode/dusk-dashboard\",\n    \"description\": \"A beautiful dashboard for your Laravel Dusk tests\",\n    \"keywords\": [\n        \"beyondcode\",\n        \"dusk-dashboard\"\n    ],\n    \"homepage\": \"https://github.com/beyondcode/dusk-dashboard\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Marcel Pociot\",\n            \"email\": \"marcel@beyondco.de\",\n            \"homepage\": \"https://beyondcode.de\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"^7.2\",\n        \"cboden/ratchet\": \"^0.4.1\",\n        \"clue/buzz-react\": \"^2.5\",\n        \"guzzlehttp/guzzle\": \"^7.0.1\",\n        \"illuminate/console\": \"5.6.*|5.7.*|5.8.*|6.*|7.*|8.*\",\n        \"illuminate/support\": \"5.6.*|5.7.*|5.8.*|6.*|7.*|8.*\",\n        \"laravel/dusk\": \"^4.0|^5.0|^6.0\",\n        \"yosymfony/resource-watcher\": \"^2.0\"\n    },\n    \"require-dev\": {\n        \"larapack/dd\": \"^1.0\",\n        \"phpunit/phpunit\": \"^7.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"BeyondCode\\\\DuskDashboard\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"BeyondCode\\\\DuskDashboard\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"scripts\": {\n        \"test\": \"vendor/bin/phpunit\",\n        \"test-coverage\": \"vendor/bin/phpunit --coverage-html coverage\"\n\n    },\n    \"config\": {\n        \"sort-packages\": true\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"BeyondCode\\\\DuskDashboard\\\\DuskDashboardServiceProvider\"\n            ]\n        }\n    }\n}\n"
  },
  {
    "path": "config/dusk-dashboard.php",
    "content": "<?php\n\nreturn [\n    /*\n     * You may specify which host the dashboard runs on in case it differs from the APP_URL\n     */\n    'host' => env('DUSK_DASHBOARD_URL', env('APP_URL', 'http://localhost')),\n];\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         colors=\"true\"\n         verbose=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\">\n    <testsuites>\n        <testsuite name=\"BeyondCode Test Suite\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">src/</directory>\n        </whitelist>\n    </filter>\n    <logging>\n        <log type=\"tap\" target=\"build/report.tap\"/>\n        <log type=\"junit\" target=\"build/report.junit.xml\"/>\n        <log type=\"coverage-html\" target=\"build/coverage\" charset=\"UTF-8\" yui=\"true\" highlight=\"true\"/>\n        <log type=\"coverage-text\" target=\"build/coverage.txt\"/>\n        <log type=\"coverage-clover\" target=\"build/logs/clover.xml\"/>\n    </logging>\n</phpunit>\n"
  },
  {
    "path": "resources/views/index.html",
    "content": "<!doctype html>\n<html class=\"h-full\">\n<head>\n    <!-- Meta Information -->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\n    <link href=\"https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css\" rel=\"stylesheet\">\n    <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.5.0/css/all.css\" integrity=\"sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU\" crossorigin=\"anonymous\">\n\n    <title>Dusk Dashboard</title>\n</head>\n<body class=\"h-full\">\n<div id=\"dashboard\" class=\"max-h-screen h-screen\">\n    <div class=\"h-full\">\n        <div class=\"flex items-center h-16 px-2 w-full absolute pin-t\" style=\"z-index: 900\">\n            <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>\n\n            <h4 class=\"mb-0 ml-3\"><strong>Laravel</strong> Dusk Dashboard</h4>\n\n            <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\"\n                    @click.prevent=\"startTests\" title=\"Start Tests\">\n                <span v-if=\"testsRunning\">Watching...</span>\n                <span v-else>Start Watching</span>\n            </button>\n        </div>\n        <div class=\"flex h-full w-full absolute pt-16 pin-t\">\n            <div class=\"w-1/4 h-full overflow-y-scroll\">\n                <div class=\"block sticky ml-4\">\n\n                    <div class=\"inline-flex mb-4 items-center justify-center w-full\">\n                        <button @click=\"filter = null\" class=\"focus:outline-none hover:bg-gray-500 text-gray-800 font-bold py-2 px-4 rounded-l\" :class=\"{\n                            'bg-gray-500': filter === null,\n                            'bg-gray-400': filter !== null\n                        }\">\n                            All ({{ _.size(groupedEvents) }})\n                        </button>\n                        <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=\"{\n                            'bg-gray-500': filter === 'success',\n                            'bg-gray-400': filter !== 'success'\n                        }\">\n                            Success ({{ _.size(success) }})\n                        </button>\n                        <button @click=\"filter = 'failure'\" class=\"focus:outline-none hover:bg-gray-500 text-gray-800 font-bold py-2 px-4 rounded-r\" :class=\"{\n                            'bg-gray-500': filter === 'failure',\n                            'bg-gray-400': filter !== 'failure'\n                        }\">\n                            Failures ({{ _.size(failures) }})\n                        </button>\n                    </div>\n\n                    <ul class=\"list-reset\">\n                        <li class=\"list-group-item\" v-for=\"(events, test) in filteredEvents\">\n                            <p class=\"my-3 lg:mb-2 text-gray-500 uppercase tracking-wider font-bold text-sm lg:text-xs\"\n                               @click=\"expand(test)\"\n                               style=\"white-space: nowrap\"\n                               :class=\"{\n                                                'text-green-600': ! events[events.length-1].failure,\n                                                'text-red-600': events[events.length-1].failure,\n                                            }\">\n                                <i class=\"fa fa-check\" :class=\"{\n                                                'text-green-600 fa-check': ! events[events.length-1].failure,\n                                                'text-red-600 fa-times': events[events.length-1].failure,\n                                            }\"></i>\n                                {{ test }}\n                                    <i class=\"fa fa-chevron-down\"\n                                          v-show=\"!isExpanded(test)\">\n                                    </i>\n                                    <i class=\"fa fa-chevron-up\"\n                                          v-show=\"isExpanded(test)\">\n                                    </i>\n                            </p>\n\n                            <ul class=\"list-reset mr-4 font-mono text-xs\"\n                                v-show=\"isExpanded(test)\">\n                                <li class=\"cursor-pointer bg-gray-400 hover:bg-gray-100 p-2\"\n                                    v-for=\"(event, key) in events\"\n                                    @mouseover=\"hoverEvent(event)\"\n                                    @click=\"pinEvent(event)\"\n                                    :class=\"{'bg-gray-100': event === pinnedEvent}\">\n                                    <div class=\"flex items-center\">\n                                        <div class=\"mr-2 flex items-center\">\n                                            <div class=\"font-mono text-gray-600 pr-2\" style=\"min-width: 25px;\">\n                                                <i class=\"fa fa-thumbtack text-gray-500\" v-if=\"event === hoveredEvent || event === pinnedEvent\"></i>\n                                                <span v-else>{{ key+1 }}</span>\n                                            </div>\n                                            <i class=\"fa fa-check\" :class=\"{\n                                                'text-green-600 fa-check': ! event.failure,\n                                                'text-red-600 fa-times': event.failure,\n                                            }\"></i>\n                                        </div>\n                                        <span style=\"min-width: 180px;\">{{ event.name }}</span>\n                                        <span>{{ event.arguments[0] || '' }}</span>\n                                    </div>\n                                    <div class=\"flex flex-col my-2\" v-if=\"event === pinnedEvent && event.arguments.length > 1\">\n                                        <div class=\"my-2\" v-for=\"argument in event.arguments\">\n                                            {{ argument }}\n                                        </div>\n                                    </div>\n                                    <div class=\"mt-2 w-full text-red-800\" v-if=\"event.failure\">\n                                        {{ event.failure }}\n                                    </div>\n                                </li>\n                            </ul>\n                        </li>\n                    </ul>\n                </div>\n            </div>\n            <div class=\"w-3/4 bg-gray-500 h-full flex justify-center\">\n                <div class=\"mt-4 flex flex-col shadow-2xl\" style=\"width: 1000px; height: 680px;\">\n                    <div class=\"rounded-sm bg-gray-400 p-2 h-12 justify-center items-center\">\n                        <input type=\"text\" class=\"focus:outline-none h-full w-full font-mono p-2\" v-model=\"activeEvent.path\" readonly>\n                    </div>\n                    <div class=\"h-full w-full flex flex-col\">\n                        <iframe ref=\"iframe\" id=\"iframe\" frameborder=\"0\" class=\"h-full w-full block bg-white overflow-auto\"></iframe>\n                        <div class=\"flex bg-gray-600 text-white rounded-sm h-12 justify-center items-center text-center\" v-if=\"activeEvent\">\n                            <span class=\"mr-4\">DOM Snapshot</span>\n                            <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>\n                            <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>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<script src=\"https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.slim.min.js\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js\"></script>\n\n<script>\n    var conn;\n\n    new Vue({\n        el: '#dashboard',\n\n        data: {\n            filter: null,\n            activeEvent: {},\n            hoveredEvent: null,\n            pinnedEvent: null,\n            events: [],\n            expanded: [],\n            testsRunning : false,\n            currentTestName : '',\n            timer: null,\n        },\n\n        computed: {\n            groupedEvents: function() {\n                return _.groupBy(this.events, 'test');\n            },\n\n            filteredEvents: function() {\n                if (this.filter === 'failure') {\n                    return this.failures;\n                } else if (this.filter === 'success') {\n                    return this.success;\n                }\n                return this.groupedEvents;\n            },\n\n            failures: function() {\n                return _.pickBy(this.groupedEvents, (values, key) => {\n                    return values[values.length -1].failure;\n                });\n            },\n\n            success: function() {\n                return _.pickBy(this.groupedEvents, (values, key) => {\n                    return ! values[values.length -1].failure;\n                });\n            }\n        },\n\n        methods: {\n            expand(key) {\n                if (!this.expanded.includes(key)) {\n                    this.expanded.push(key);\n                } else {\n                    for( var i = 0; i < this.expanded.length; i++){\n                        if ( this.expanded[i] === key) {\n                            this.expanded.splice(i, 1);\n                        }\n                    }\n                }\n            },\n\n            isExpanded(key)\n            {\n                if (this.expanded.includes(key)) {\n                    return true\n                }\n                return false;\n            },\n\n            hoverEvent: function(event) {\n                this.hoveredEvent = event;\n            },\n\n            loadEvent: function(event) {\n                if (this.activeEvent !== event) {\n                    this.activeEvent = event;\n                    this.loadHtml(event.html);\n                }\n            },\n\n            pinEvent: function(event) {\n                if (this.pinnedEvent === event) {\n                    this.pinnedEvent = null;\n                } else {\n                    this.pinnedEvent = event;\n                    this.loadEvent(event);\n                }\n            },\n\n            startTests: function() {\n                this.testsRunning = true;\n                this.events = [];\n\n                conn.send(JSON.stringify({\n                    method: 'startTests'\n                }));\n            },\n\n            loadBefore: function() {\n                this.loadHtml(this.activeEvent.before);\n            },\n\n            loadAfter: function() {\n                this.loadHtml(this.activeEvent.html);\n            },\n\n            loadHtml: function(html) {\n                var iframeDocument = window.document.getElementById('iframe').contentWindow.document;\n\n                iframeDocument.open('text/html', 'replace');\n                iframeDocument.write(html);\n                iframeDocument.close();\n\n                setTimeout(function () {\n                    this.highlightEvent(this.activeEvent);\n                }.bind(this), 50);\n            },\n\n            highlightEvent: function(event) {\n                let name = event.name;\n                let selector;\n\n                switch (name) {\n                    case 'click':\n                        selector = event.arguments[0];\n                        break;\n                    case 'select':\n                    case 'radio':\n                    case 'type':\n                        selector = `#${event.arguments[0]},`;\n                        selector += `input[name=\"${event.arguments[0]}\"],`;\n                        selector += `select[name=\"${event.arguments[0]}\"],`;\n                        selector += `textarea[name=\"${event.arguments[0]}\"]`;\n                        break;\n                    case 'press':\n                        selector = '';\n                        selector += `input[type=submit][name='${event.arguments[0]}'],`;\n                        selector += `input[type=submit][value='${event.arguments[0]}'],`;\n                        selector += `input[type=button][value='${event.arguments[0]}'],`;\n                        selector += `button[name='${event.arguments[0]}'],`;\n                        selector += `button:contains('${event.arguments[0]}')`;\n                        break;\n                    case 'mouseover':\n                        selector = event.arguments[0];\n                        break;\n                }\n\n                if (selector) {\n                    try {\n                        document.getElementById('iframe')\n                            .contentWindow\n                            .jQuery(selector)\n                            .css('outline', '5px solid red');\n                    } catch (e) {}\n                }\n            }\n        },\n\n        mounted: function() {\n            conn = new WebSocket(`ws://${window.location.hostname}:${window.location.port}/socket`);\n\n            conn.onmessage = (e) => {\n                let message = JSON.parse(e.data);\n\n                window.clearTimeout(this.timer);\n                this.timer = setTimeout(function() {\n                    alert(\"Finished\");\n                    }, 2000);\n\n                if (message.data.test !== this.currentTestName) {\n                    // new test has started\n                }\n                this.currentTestName = message.data.test;\n\n                if (message.name === 'dusk-event') {\n                    this.events.push(message.data);\n\n                    if (this.pinnedEvent === null) {\n                        this.loadEvent(message.data);\n                    }\n\n\n                }\n\n                if (message.name === 'dusk-failure') {\n                    this.events[(this.events.length -1 )].failure = message.data.message;\n                }\n\n                if (message.name === 'dusk-reset') {\n                    this.events = [];\n                }\n            };\n        }\n    });\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "src/Action.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nclass Action\n{\n    protected $name;\n\n    protected $arguments;\n\n    protected $html;\n\n    protected $previousHtml;\n\n    protected $path;\n\n    public function __construct(string $name, array $arguments, string $html, string $path)\n    {\n        $this->name = $name;\n\n        $this->arguments = $arguments;\n\n        $this->html = $html;\n\n        $this->path = $path;\n    }\n\n    public function getName()\n    {\n        return $this->name;\n    }\n\n    public function getHtml()\n    {\n        return $this->html;\n    }\n\n    public function setPreviousHtml(?string $html)\n    {\n        $this->previousHtml = $html;\n    }\n\n    public function getPreviousHtml()\n    {\n        return $this->previousHtml;\n    }\n\n    public function getArguments()\n    {\n        return $this->arguments;\n    }\n\n    public function getPath()\n    {\n        return $this->path;\n    }\n}\n"
  },
  {
    "path": "src/BrowserActionCollector.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse BeyondCode\\DuskDashboard\\Console\\StartDashboardCommand;\nuse BeyondCode\\DuskDashboard\\Dusk\\Browser;\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\RequestOptions;\n\nclass BrowserActionCollector\n{\n    /** @var Client */\n    protected $client;\n\n    protected $testName;\n\n    public function __construct($testName)\n    {\n        $this->testName = $testName;\n\n        $this->client = new Client();\n    }\n\n    public function collect(string $action, array $arguments, Browser $browser, string $previousHtml = null)\n    {\n        $path = parse_url($browser->driver->getCurrentURL(), PHP_URL_PATH) ?? '';\n\n        $action = new Action($action, $arguments, $browser->getCurrentPageSource(), $path);\n\n        $action->setPreviousHtml($previousHtml);\n\n        $this->pushAction('dusk-event', [\n            'test' => $this->testName,\n            'path' => $action->getPath(),\n            'name' => $action->getName(),\n            'arguments' => $action->getArguments(),\n            'before' => $action->getPreviousHtml(),\n            'html' => $action->getHtml(),\n        ]);\n\n        $this->processPerformanceLog($browser);\n    }\n\n    protected function processPerformanceLog(Browser $browser)\n    {\n        $logs = collect([]);\n\n        try {\n            $logs = collect($browser->driver->manage()->getLog('performance'));\n        } catch (\\Exception $e) {\n            // performance logging might be disabled.\n        }\n\n        $allowedMethods = [\n            'Network.requestWillBeSent',\n            'Network.responseReceived',\n        ];\n\n        $logs\n            ->map(function ($log) {\n                return json_decode($log['message']);\n            })\n            ->filter(function ($log) use ($allowedMethods) {\n                $method = data_get($log, 'message.method');\n\n                $type = data_get($log, 'message.params.type');\n\n                return in_array($method, $allowedMethods) && $type === 'XHR';\n            })->groupBy(function ($log) {\n                if (data_get($log, 'message.method') === 'Network.requestWillBeSent') {\n                    return data_get($log, 'message.requestId');\n                }\n\n                return data_get($log, 'params.requestId');\n            })->map(function ($log) use ($browser) {\n                $this->pushPerformanceLog($log->toArray(), $browser);\n            });\n    }\n\n    protected function pushAction(string $name, array $payload)\n    {\n        try {\n            $this->client->post('http://127.0.0.1:'.StartDashboardCommand::PORT.'/events', [\n                RequestOptions::JSON => [\n                    'channel' => 'dusk-dashboard',\n                    'name' => $name,\n                    'data' => $payload,\n                ],\n            ]);\n        } catch (\\Exception $e) {\n            // Dusk-Dashboard Server might be turned off. No need to panic!\n        }\n    }\n\n    protected function pushPerformanceLog(array $log, Browser $browser)\n    {\n        $request = $log[0];\n        $response = $log[1];\n\n        $url = parse_url(data_get($request, 'message.params.request.url'));\n\n        $this->pushAction('dusk-event', [\n            'test' => $this->testName,\n            'name' => 'XHR',\n            'arguments' => [\n                data_get($request, 'message.params.request.method').' '.\n                $url['path'].' '.\n                data_get($response, 'message.params.response.status').' '.\n                data_get($response, 'message.params.response.statusText'),\n            ],\n            'html' => $browser->getCurrentPageSource(),\n            'logs' => $log,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Console/StartDashboardCommand.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Console;\n\nuse BeyondCode\\DuskDashboard\\DuskProcessFactory;\nuse BeyondCode\\DuskDashboard\\Ratchet\\Http\\DashboardController;\nuse BeyondCode\\DuskDashboard\\Ratchet\\Http\\EventController;\nuse BeyondCode\\DuskDashboard\\Ratchet\\Server\\App;\nuse BeyondCode\\DuskDashboard\\Ratchet\\Socket;\nuse BeyondCode\\DuskDashboard\\Watcher;\nuse Clue\\React\\Buzz\\Browser;\nuse Illuminate\\Console\\Command;\nuse Ratchet\\WebSocket\\WsServer;\nuse React\\EventLoop\\Factory as LoopFactory;\nuse React\\EventLoop\\LoopInterface;\nuse Symfony\\Component\\Finder\\Finder;\nuse Symfony\\Component\\Routing\\Route;\n\nclass StartDashboardCommand extends Command\n{\n    const PORT = 9773;\n\n    protected $signature = 'dusk:dashboard';\n\n    protected $description = 'Start the Laravel Dusk Dashboard';\n\n    /**\n     * Create a new command instance.\n     *\n     * @return void\n     */\n    public function __construct()\n    {\n        parent::__construct();\n\n        $this->ignoreValidationErrors();\n    }\n\n    /** @var App */\n    protected $app;\n\n    /** @var LoopInterface */\n    protected $loop;\n\n    public function handle()\n    {\n        $url = parse_url(config('dusk-dashboard.host', config('app.url')));\n\n        $this->loop = LoopFactory::create();\n\n        $this->loop->futureTick(function () use ($url) {\n            $dashboardUrl = 'http://'.$url['host'].':'.self::PORT.'/dashboard';\n\n            $this->info('Started Dusk Dashboard on port '.self::PORT);\n\n            $this->info('Your Dusk tests are now being watched.');\n\n            $this->info('If the dashboard does not automatically open, visit: '.$dashboardUrl);\n\n            $this->tryToOpenInBrowser($dashboardUrl);\n        });\n\n        $this->createTestWatcher();\n\n        $this->createApp($url);\n    }\n\n    protected function addRoutes()\n    {\n        $eventRoute = new Route('/events', ['_controller' => new EventController()], [], [], null, [], ['POST']);\n\n        $this->app->routes->add('events', $eventRoute);\n\n        $dashboardRoute = new Route('/dashboard', ['_controller' => new DashboardController()], [], [], null, [], ['GET']);\n\n        $this->app->routes->add('dashboard', $dashboardRoute);\n    }\n\n    protected function createTestWatcher()\n    {\n        $finder = (new Finder)\n            ->name('*.php')\n            ->files()\n            ->in($this->getTestSuitePath());\n\n        (new Watcher($finder, $this->loop))->startWatching(function () {\n            $client = new Browser($this->loop);\n\n            $client->post('http://127.0.0.1:'.self::PORT.'/events', [\n                'Content-Type' => 'application/json',\n            ], json_encode([\n                'channel' => 'dusk-dashboard',\n                'name' => 'dusk-reset',\n                'data' => [],\n            ])\n            );\n\n            $process = DuskProcessFactory::make();\n\n            $process->start();\n        });\n    }\n\n    protected function getTestSuitePath()\n    {\n        $directories = [];\n\n        if (file_exists(base_path('phpunit.dusk.xml'))) {\n            $xml = simplexml_load_file(base_path('phpunit.dusk.xml'));\n\n            foreach ($xml->testsuites->testsuite as $testsuite) {\n                $directories[] = (string) $testsuite->directory;\n            }\n        } else {\n            $directories[] = base_path('tests/Browser');\n        }\n\n        return $directories;\n    }\n\n    protected function createApp(array $url)\n    {\n        $socket = new Socket();\n\n        $this->app = new App($url['host'], self::PORT, '0.0.0.0', $this->loop);\n\n        $this->app->route('/socket', new WsServer($socket), ['*']);\n\n        $this->addRoutes();\n\n        $this->app->run();\n    }\n\n    protected function tryToOpenInBrowser($url)\n    {\n        if (PHP_OS === 'Darwin') {\n            exec('open '.$url);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Browser.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk;\n\nuse BeyondCode\\DuskDashboard\\BrowserActionCollector;\n\nclass Browser extends \\Laravel\\Dusk\\Browser\n{\n    use Concerns\\InteractsWithAuthentication,\n        Concerns\\InteractsWithCookies,\n        Concerns\\InteractsWithElements,\n        Concerns\\InteractsWithJavascript,\n        Concerns\\InteractsWithMouse,\n        Concerns\\MakesAssertions,\n        Concerns\\MakesUrlAssertions,\n        Concerns\\WaitsForElements;\n\n    /** @var BrowserActionCollector */\n    protected $actionCollector;\n\n    public function setActionCollector(BrowserActionCollector $collector)\n    {\n        $this->actionCollector = $collector;\n    }\n\n    /**\n     * @return BrowserActionCollector|null\n     */\n    public function getActionCollector()\n    {\n        return $this->actionCollector;\n    }\n\n    /** {@inheritdoc} */\n    public function visit($url)\n    {\n        $browser = parent::visit($url);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function visitRoute($route, $parameters = [])\n    {\n        $browser = parent::visitRoute($route, $parameters);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function refresh()\n    {\n        $browser = parent::refresh();\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    public function getCurrentPageSource()\n    {\n        $this->ensurejQueryIsAvailable();\n\n        $this->restoreHtml();\n\n        return $this->driver->executeScript('return document.documentElement.innerHTML;');\n    }\n\n    protected function restoreHtml()\n    {\n        $this->driver->executeScript(\"jQuery('input').attr('value', function() { return jQuery(this).val(); });\");\n\n        $this->driver->executeScript(\"jQuery('input[type=checkbox]').each(function() { jQuery(this).attr('checked', jQuery(this).prop(\\\"checked\\\")); });\");\n\n        $this->driver->executeScript(\"jQuery('textarea').each(function() { jQuery(this).html(jQuery(this).val()); });\");\n\n        $this->driver->executeScript(\"jQuery('input[type=radio]').each(function() { jQuery(this).attr('checked', this.checked); });\");\n\n        $this->driver->executeScript(\"jQuery('select option').each(function() { jQuery(this).attr('selected', this.selected); });\");\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithAuthentication.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithAuthentication\n{\n    /** {@inheritdoc} */\n    public function login()\n    {\n        $browser = parent::login();\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function loginAs($userId, $guard = null)\n    {\n        $browser = parent::loginAs($userId, $guard);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function logout($guard = null)\n    {\n        $browser = parent::logout($guard);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function assertAuthenticated($guard = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertAuthenticated($guard);\n    }\n\n    /** {@inheritdoc} */\n    public function assertGuest($guard = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertGuest($guard);\n    }\n\n    /** {@inheritdoc} */\n    public function assertAuthenticatedAs($user, $guard = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertAuthenticatedAs($user, $guard);\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithCookies.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithCookies\n{\n    /** {@inheritdoc} */\n    public function cookie($name, $value = null, $expiry = null, array $options = [])\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::cookie($name, $value, $expiry, $options);\n    }\n\n    /** {@inheritdoc} */\n    public function plainCookie($name, $value = null, $expiry = null, array $options = [])\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::plainCookie($name, $value, $expiry, $options);\n    }\n\n    /** {@inheritdoc} */\n    public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::addCookie($name, $value, $expiry, $options, $encrypt);\n    }\n\n    /** {@inheritdoc} */\n    public function deleteCookie($name)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::deleteCookie($name);\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithElements.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithElements\n{\n    /** {@inheritdoc} */\n    public function elements($selector)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::elements($selector);\n    }\n\n    /** {@inheritdoc} */\n    public function element($selector)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::element($selector);\n    }\n\n    /** {@inheritdoc} */\n    public function clickLink($link, $element = 'a')\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::clickLink($link, $element);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function value($selector, $value = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::value($selector, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function text($selector)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::text($selector);\n    }\n\n    /** {@inheritdoc} */\n    public function attribute($selector, $attribute)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::attribute($selector, $attribute);\n    }\n\n    /** {@inheritdoc} */\n    public function keys($selector, ...$keys)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::keys($selector, $keys);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function type($field, $value)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::type($field, $value);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function append($field, $value)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::append($field, $value);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function clear($field)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::clear($field);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function select($field, $value = null)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::select($field, $value);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function radio($field, $value)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::radio($field, $value);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function check($field, $value = null)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::check($field, $value);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function uncheck($field, $value = null)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::uncheck($field, $value);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function attach($field, $path)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::attach($field, $path);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function press($button)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::press($button);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function pressAndWaitFor($button, $seconds = 5)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::pressAndWaitFor($button, $seconds);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function drag($from, $to)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::drag($from, $to);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function dragUp($selector, $offset)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::dragUp($selector, $offset);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function dragDown($selector, $offset)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::dragDown($selector, $offset);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function dragLeft($selector, $offset)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::dragLeft($selector, $offset);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function dragRight($selector, $offset)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::dragRight($selector, $offset);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function dragOffset($selector, $x = 0, $y = 0)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::dragOffset($selector, $x, $y);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function acceptDialog()\n    {\n        $browser = parent::acceptDialog();\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function typeInDialog($value)\n    {\n        $browser = parent::typeInDialog($value);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function dismissDialog()\n    {\n        $browser = parent::dismissDialog();\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $browser;\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithJavascript.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithJavascript\n{\n    /** {@inheritdoc} */\n    public function script($scripts)\n    {\n        $result = parent::script($scripts);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return $result;\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/InteractsWithMouse.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait InteractsWithMouse\n{\n    /** {@inheritdoc} */\n    public function moveMouse($xOffset, $yOffset)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::moveMouse($xOffset, $yOffset);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function mouseover($selector)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::mouseover($selector);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function click($selector = null)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::click($selector);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function clickAndHold()\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::clickAndHold();\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    public function doubleClick()\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::doubleClick();\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function rightClick($selector = null)\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::rightClick($selector);\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n\n    /** {@inheritdoc} */\n    public function releaseMouse()\n    {\n        $previousHtml = $this->getCurrentPageSource();\n\n        $browser = parent::releaseMouse();\n\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this, $previousHtml);\n\n        return $browser;\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/MakesAssertions.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait MakesAssertions\n{\n    /** {@inheritdoc} */\n    public function assertTitle($title)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertTitle($title);\n    }\n\n    /** {@inheritdoc} */\n    public function assertTitleContains($title)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertTitleContains($title);\n    }\n\n    /** {@inheritdoc} */\n    public function assertHasCookie($name, $decrypt = true)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertHasCookie($name, $decrypt);\n    }\n\n    /** {@inheritdoc} */\n    public function assertHasPlainCookie($name)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertHasPlainCookie($name);\n    }\n\n    /** {@inheritdoc} */\n    public function assertCookieMissing($name, $decrypt = true)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertCookieMissing($name, $decrypt);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPlainCookieMissing($name)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPlainCookieMissing($name);\n    }\n\n    /** {@inheritdoc} */\n    public function assertCookieValue($name, $value, $decrypt = true)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertCookieValue($name, $value, $decrypt);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPlainCookieValue($name, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPlainCookieValue($name, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSee($text)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSee($text);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSeeIn($selector, $text)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSeeIn($selector, $text);\n    }\n\n    /** {@inheritdoc} */\n    public function assertDontSeeIn($selector, $text)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertDontSeeIn($selector, $text);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSourceHas($code)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSourceHas($code);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSourceMissing($code)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSourceMissing($code);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSeeLink($link)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSeeLink($link);\n    }\n\n    /** {@inheritdoc} */\n    public function assertDontSeeLink($link)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertDontSeeLink($link);\n    }\n\n    /** {@inheritdoc} */\n    public function seeLink($link)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::seeLink($link);\n    }\n\n    /** {@inheritdoc} */\n    public function assertInputValue($field, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertInputValue($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertInputValueIsNot($field, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertInputValueIsNot($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function inputValue($field)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::inputValue($field);\n    }\n\n    /** {@inheritdoc} */\n    public function assertChecked($field, $value = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertChecked($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertNotChecked($field, $value = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertNotChecked($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertRadioSelected($field, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertRadioSelected($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertRadioNotSelected($field, $value = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertRadioNotSelected($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSelected($field, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSelectHasOption($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertNotSelected($field, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertNotSelected($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSelectHasOptions($field, array $values)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSelectHasOptions($field, $values);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSelectMissingOptions($field, array $values)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSelectMissingOptions($field, $values);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSelectHasOption($field, $value)\n    {\n        return $this->assertSelectHasOptions($field, [$value]);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSelectMissingOption($field, $value)\n    {\n        return $this->assertSelectMissingOptions($field, [$value]);\n    }\n\n    /** {@inheritdoc} */\n    public function selected($field, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::select($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertValue($selector, $value)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertValue($field, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertVisible($selector)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertVisible($selector);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPresent($selector)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPresent($selector);\n    }\n\n    /** {@inheritdoc} */\n    public function assertMissing($selector)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertMissing($selector);\n    }\n\n    /** {@inheritdoc} */\n    public function assertDialogOpened($message)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertDialogOpened($message);\n    }\n\n    /** {@inheritdoc} */\n    public function assertEnabled($field)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertEnabled($field);\n    }\n\n    /** {@inheritdoc} */\n    public function assertDisabled($field)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertDisabled($field);\n    }\n\n    /** {@inheritdoc} */\n    public function assertFocused($field)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertFocused($field);\n    }\n\n    /** {@inheritdoc} */\n    public function assertNotFocused($field)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertNotFocused($field);\n    }\n\n    /** {@inheritdoc} */\n    public function assertVue($key, $value, $componentSelector = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertVue($key, $value, $componentSelector);\n    }\n\n    /** {@inheritdoc} */\n    public function assertVueIsNot($key, $value, $componentSelector = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertVueIsNot($key, $value, $componentSelector);\n    }\n\n    /** {@inheritdoc} */\n    public function assertVueContains($key, $value, $componentSelector = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertVueContains($key, $value, $componentSelector);\n    }\n\n    /** {@inheritdoc} */\n    public function assertVueDoesNotContain($key, $value, $componentSelector = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertVueDoesNotContain($key, $value, $componentSelector);\n    }\n\n    /** {@inheritdoc} */\n    public function vueAttribute($componentSelector, $key)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::vueAttribute($componentSelector, $key);\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/MakesUrlAssertions.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\ntrait MakesUrlAssertions\n{\n    /** {@inheritdoc} */\n    public function assertUrlIs($url)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertUrlIs($url);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSchemeIs($scheme)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSchemeIs($scheme);\n    }\n\n    /** {@inheritdoc} */\n    public function assertSchemeIsNot($scheme)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertSchemeIsNot($scheme);\n    }\n\n    /** {@inheritdoc} */\n    public function assertHostIs($host)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertHostIs($host);\n    }\n\n    /** {@inheritdoc} */\n    public function assertHostIsNot($host)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertHostIsNot($host);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPortIs($port)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPortIs($port);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPortIsNot($port)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPortIsNot($port);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPathIs($path)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPathIs($path);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPathBeginsWith($path)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPathBeginsWith($path);\n    }\n\n    /** {@inheritdoc} */\n    public function assertPathIsNot($path)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertPathIsNot($path);\n    }\n\n    /** {@inheritdoc} */\n    public function assertFragmentIs($fragment)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertFragmentIs($fragment);\n    }\n\n    /** {@inheritdoc} */\n    public function assertFragmentBeginsWith($fragment)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertFragmentBeginsWith($fragment);\n    }\n\n    /** {@inheritdoc} */\n    public function assertFragmentIsNot($fragment)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertFragmentIsNot($fragment);\n    }\n\n    /** {@inheritdoc} */\n    public function assertRouteIs($route, $parameters = [])\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertRouteIs($route, $parameters);\n    }\n\n    /** {@inheritdoc} */\n    public function assertQueryStringHas($name, $value = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertQueryStringHas($name, $value);\n    }\n\n    /** {@inheritdoc} */\n    public function assertQueryStringMissing($name)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertQueryStringMissing($name);\n    }\n\n    /** {@inheritdoc} */\n    protected function assertHasQueryStringParameter($name)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::assertHasQueryStringParameter($name);\n    }\n}\n"
  },
  {
    "path": "src/Dusk/Concerns/WaitsForElements.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Dusk\\Concerns;\n\nuse Closure;\n\ntrait WaitsForElements\n{\n    /** {@inheritdoc} */\n    public function whenAvailable($selector, Closure $callback, $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::whenAvailable($selector, $callback, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitFor($selector, $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitFor($selector, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitUntilMissing($selector, $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitUntilMissing($selector, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitForText($text, $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitForText($text, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitForLink($link, $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitForLink($link, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitForLocation($path, $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitForLocation($path, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitForRoute($route, $parameters = [], $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitForRoute($route, $parameters, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitUntil($script, $seconds = null, $message = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitUntil($script, $seconds, $message);\n    }\n\n    /** {@inheritdoc} */\n    public function waitForDialog($seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitForDialog($seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitForReload($callback = null, $seconds = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitForReload($callback, $seconds);\n    }\n\n    /** {@inheritdoc} */\n    public function waitUsing($seconds, $interval, Closure $callback, $message = null)\n    {\n        $this->actionCollector->collect(__FUNCTION__, func_get_args(), $this);\n\n        return parent::waitUsing($seconds, $interval, $callback, $message);\n    }\n}\n"
  },
  {
    "path": "src/DuskDashboardServiceProvider.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse Illuminate\\Support\\ServiceProvider;\n\nclass DuskDashboardServiceProvider extends ServiceProvider\n{\n    /**\n     * Register the application services.\n     */\n    public function register()\n    {\n        $this->commands([\n            Console\\StartDashboardCommand::class,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/DuskProcessFactory.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse Symfony\\Component\\Process\\Process;\n\nclass DuskProcessFactory\n{\n    /**\n     * Create new process to run Dusk, passing the same arguments Dashboard command received.\n     *\n     * @return \\Symfony\\Component\\Process\\Process\n     */\n    public static function make()\n    {\n        return new Process(array_merge(self::binary(), self::arguments()), base_path());\n    }\n\n    /**\n     * Dusk command represented as array.\n     *\n     * @return array\n     */\n    protected static function binary()\n    {\n        return [PHP_BINARY, 'artisan', 'dusk'];\n    }\n\n    /**\n     * Arguments that were given to dusk:dashboard.\n     *\n     * @return array\n     */\n    protected static function arguments()\n    {\n        return array_slice($_SERVER['argv'], 2);\n    }\n}\n"
  },
  {
    "path": "src/Ratchet/Http/Controller.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Http;\n\nuse Exception;\nuse Ratchet\\ConnectionInterface;\nuse Ratchet\\Http\\HttpServerInterface;\n\nabstract class Controller implements HttpServerInterface\n{\n    public function onClose(ConnectionInterface $connection)\n    {\n    }\n\n    public function onError(ConnectionInterface $connection, Exception $e)\n    {\n    }\n\n    public function onMessage(ConnectionInterface $from, $msg)\n    {\n    }\n}\n"
  },
  {
    "path": "src/Ratchet/Http/DashboardController.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Http;\n\nuse GuzzleHttp\\Psr7\\Response;\nuse function GuzzleHttp\\Psr7\\str;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Ratchet\\ConnectionInterface;\n\nclass DashboardController extends Controller\n{\n    public function onOpen(ConnectionInterface $connection, RequestInterface $request = null)\n    {\n        $connection->send(\n            str(new Response(\n                200,\n                ['Content-Type' => 'text/html'],\n                file_get_contents(__DIR__.'/../../../resources/views/index.html')\n            ))\n        );\n\n        $connection->close();\n    }\n}\n"
  },
  {
    "path": "src/Ratchet/Http/EventController.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Http;\n\nuse BeyondCode\\DuskDashboard\\Ratchet\\Socket;\nuse Exception;\nuse GuzzleHttp\\Psr7\\Response;\nuse function GuzzleHttp\\Psr7\\str;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Ratchet\\ConnectionInterface;\n\nclass EventController extends Controller\n{\n    public function onOpen(ConnectionInterface $conn, RequestInterface $request = null)\n    {\n        try {\n\n            /*\n             * This is the post payload from our PHPUnit tests.\n             * Send it to the connected connections.\n             */\n            foreach (Socket::$connections as $connection) {\n                $connection->send($request->getBody());\n            }\n\n            $conn->send(str(new Response(200)));\n        } catch (Exception $e) {\n            $conn->send(str(new Response(500, [], $e->getMessage())));\n        }\n\n        $conn->close();\n    }\n}\n"
  },
  {
    "path": "src/Ratchet/Server/App.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Server;\n\nuse Ratchet\\Http\\Router;\nuse Ratchet\\Server\\IoServer;\nuse React\\EventLoop\\LoopInterface;\nuse React\\Socket\\Server as Reactor;\nuse Symfony\\Component\\Routing\\Matcher\\UrlMatcher;\nuse Symfony\\Component\\Routing\\RequestContext;\nuse Symfony\\Component\\Routing\\RouteCollection;\n\nclass App extends \\Ratchet\\App\n{\n    public function __construct($httpHost, $port, $address, LoopInterface $loop)\n    {\n        $this->httpHost = $httpHost;\n        $this->port = $port;\n\n        $socket = new Reactor($address.':'.$port, $loop);\n\n        $this->routes = new RouteCollection;\n\n        $urlMatcher = new UrlMatcher($this->routes, new RequestContext);\n\n        $router = new Router($urlMatcher);\n\n        $httpServer = new HttpServer($router);\n\n        $this->_server = new IoServer($httpServer, $socket, $loop);\n    }\n}\n"
  },
  {
    "path": "src/Ratchet/Server/HttpServer.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet\\Server;\n\nuse Ratchet\\Http\\HttpServerInterface;\n\nclass HttpServer extends \\Ratchet\\Http\\HttpServer\n{\n    public function __construct(HttpServerInterface $component)\n    {\n        parent::__construct($component);\n\n        $this->_reqParser->maxSize = 5242880;\n    }\n}\n"
  },
  {
    "path": "src/Ratchet/Socket.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Ratchet;\n\nuse BeyondCode\\DuskDashboard\\DuskProcessFactory;\nuse Ratchet\\ConnectionInterface;\nuse Ratchet\\RFC6455\\Messaging\\MessageInterface;\nuse Ratchet\\WebSocket\\MessageComponentInterface;\n\nclass Socket implements MessageComponentInterface\n{\n    public static $connections = [];\n\n    public function onOpen(ConnectionInterface $connection)\n    {\n        self::$connections[] = $connection;\n    }\n\n    public function onMessage(ConnectionInterface $from, MessageInterface $msg)\n    {\n        $data = json_decode($msg);\n\n        if ($data->method === 'startTests') {\n            $process = DuskProcessFactory::make();\n\n            $process->start();\n        }\n    }\n\n    public function onClose(ConnectionInterface $connection)\n    {\n    }\n\n    public function onError(ConnectionInterface $connection, \\Exception $e)\n    {\n    }\n}\n"
  },
  {
    "path": "src/Testing/TestCase.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard\\Testing;\n\nuse BeyondCode\\DuskDashboard\\BrowserActionCollector;\nuse BeyondCode\\DuskDashboard\\Console\\StartDashboardCommand;\nuse BeyondCode\\DuskDashboard\\Dusk\\Browser;\nuse Closure;\nuse Facebook\\WebDriver\\Chrome\\ChromeOptions;\nuse Facebook\\WebDriver\\Remote\\DesiredCapabilities;\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\RequestOptions;\nuse Laravel\\Dusk\\TestCase as BaseTestCase;\nuse Throwable;\n\nabstract class TestCase extends BaseTestCase\n{\n    /**\n     * Create a new Browser instance.\n     *\n     * @param  \\Facebook\\WebDriver\\Remote\\RemoteWebDriver  $driver\n     * @return \\BeyondCode\\DuskDashboard\\Dusk\\Browser\n     */\n    protected function newBrowser($driver)\n    {\n        return new Browser($driver);\n    }\n\n    /**\n     * Create the browser instances needed for the given callback.\n     *\n     * @param  \\Closure  $callback\n     * @return array\n     * @throws \\ReflectionException\n     */\n    protected function createBrowsersFor(Closure $callback)\n    {\n        $browsers = parent::createBrowsersFor($callback);\n\n        foreach ($browsers as $browser) {\n            $browser->setActionCollector(new BrowserActionCollector($this->getTestName()));\n        }\n\n        static::$browsers = $browsers;\n\n        return static::$browsers;\n    }\n\n    protected function getTestName()\n    {\n        return class_basename(static::class).'::'.$this->getName();\n    }\n\n    protected function enableNetworkLogging(DesiredCapabilities $capabilities): DesiredCapabilities\n    {\n        $chromeOptions = $capabilities->getCapability(ChromeOptions::CAPABILITY);\n\n        $perfLoggingPrefs = new \\stdClass();\n        $perfLoggingPrefs->enableNetwork = true;\n\n        $chromeOptions->setExperimentalOption('perfLoggingPrefs', $perfLoggingPrefs);\n\n        $capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions);\n\n        $loggingPrefs = new \\stdClass();\n        $loggingPrefs->browser = 'ALL';\n        $loggingPrefs->performance = 'ALL';\n\n        $capabilities->setCapability('loggingPrefs', $loggingPrefs);\n\n        return $capabilities;\n    }\n\n    protected function onNotSuccessfulTest(Throwable $t): void\n    {\n        try {\n            (new Client())->post('http://127.0.0.1:'.StartDashboardCommand::PORT.'/events', [\n                RequestOptions::JSON => [\n                    'channel' => 'dusk-dashboard',\n                    'name' => 'dusk-failure',\n                    'data' => [\n                        'message' => $t->getMessage(),\n                    ],\n                ],\n            ]);\n        } catch (\\Exception $e) {\n            // Dashboard is offline\n        } finally {\n            parent::onNotSuccessfulTest($t);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Watcher.php",
    "content": "<?php\n\nnamespace BeyondCode\\DuskDashboard;\n\nuse Closure;\nuse React\\EventLoop\\LoopInterface;\nuse Symfony\\Component\\Finder\\Finder;\nuse Yosymfony\\ResourceWatcher\\Crc32ContentHash;\nuse Yosymfony\\ResourceWatcher\\ResourceCacheMemory;\nuse Yosymfony\\ResourceWatcher\\ResourceWatcher;\n\nclass Watcher\n{\n    /** @var \\Symfony\\Component\\Finder\\Finder */\n    protected $finder;\n\n    /** @var \\React\\EventLoop\\LoopInterface */\n    protected $loop;\n\n    public function __construct(Finder $finder, LoopInterface $loop)\n    {\n        $this->finder = $finder;\n        $this->loop = $loop;\n    }\n\n    public function startWatching(Closure $callback)\n    {\n        $hashContent = new Crc32ContentHash();\n        $watcher = new ResourceWatcher(new ResourceCacheMemory(), $this->finder, $hashContent);\n\n        $this->loop->addPeriodicTimer(1 / 2, function () use ($watcher, $callback) {\n            $watcher_result = $watcher->findChanges();\n\n            if ($watcher_result->hasChanges()) {\n                call_user_func($callback);\n            }\n        });\n    }\n}\n"
  }
]