[Laravel 6] Add support for Laravel Dusk tests (#4919)

This commit is contained in:
Ben Thomson 2020-02-03 12:21:04 +08:00 committed by GitHub
parent f700e236d1
commit 65c3a88179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 1755 additions and 748 deletions

38
.env.dusk Normal file
View File

@ -0,0 +1,38 @@
APP_ENV=dusk
APP_DEBUG=true
APP_URL=http://127.0.0.1:8000
APP_KEY=base64:R2w4QUhGcWxobnpjcHhlSkd0MHpMTjVxZTVuZ1BkaUM=
DB_CONNECTION=sqlite
DB_HOST=
DB_PORT=
DB_DATABASE=storage/dusk.sqlite
DB_USERNAME=
DB_PASSWORD=
REDIS_HOST=
REDIS_PASSWORD=
REDIS_PORT=
DB_USE_CONFIG_FOR_TESTING=false
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
MAIL_DRIVER=array
MAIL_HOST=
MAIL_PORT=
MAIL_ENCRYPTION=tls
MAIL_USERNAME=
MAIL_PASSWORD=
ROUTES_CACHE=false
ASSET_CACHE=false
DATABASE_TEMPLATES=false
LINK_POLICY=detect
ENABLE_CSRF=false
# Uncomment the following to use custom authentication credentials for tests
#DUSK_ADMIN_USER=
#DUSK_ADMIN_PASS=

View File

@ -6,25 +6,18 @@ on:
jobs:
codeQuality:
runs-on: ubuntu-latest
name: PHP
name: PHPCS
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install PHP
uses: shivammathur/setup-php@master
- name: Install PHP and PHP Code Sniffer
uses: shivammathur/setup-php@v1
with:
php-version: 7.2
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-suggest
- name: Reset October modules and library
run: |
git reset --hard HEAD
rm -rf ./vendor/october/rain
wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip
unzip ./vendor/october/develop.zip -d ./vendor/october
mv ./vendor/october/library-develop ./vendor/october/rain
composer dump-autoload
php-version: '7.3'
tools: phpcs
- name: Setup problem matcher for PHPCS
run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json"
- name: Run code quality checks
run: |
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" && git fetch
./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD)
phpcs --colors -nq --report="checkstyle" --extensions="php" $(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }} HEAD)

View File

@ -9,23 +9,16 @@ on:
jobs:
codeQuality:
runs-on: ubuntu-latest
name: PHP
name: PHPCS
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install PHP
uses: shivammathur/setup-php@master
- name: Install PHP and PHP Code Sniffer
uses: shivammathur/setup-php@v1
with:
php-version: 7.2
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-suggest
- name: Reset October modules and library
run: |
git reset --hard HEAD
rm -rf ./vendor/october/rain
wget https://github.com/octobercms/library/archive/develop.zip -O ./vendor/october/develop.zip
unzip ./vendor/october/develop.zip -d ./vendor/october
mv ./vendor/october/library-develop ./vendor/october/rain
composer dump-autoload
php-version: '7.3'
tools: phpcs
- name: Setup problem matcher for PHPCS
run: echo "::add-matcher::${{ github.workspace }}/.github/workflows/matchers/phpcs-matcher.json"
- name: Run code quality checks
run: ./vendor/bin/phpcs --colors -nq --report="full" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }})
run: phpcs --colors -nq --report="checkstyle" --extensions="php" $(git show --name-only --pretty="" --diff-filter=ACMR ${{ github.sha }})

View File

@ -1,24 +0,0 @@
name: Tests
on:
push:
branches:
- master
- develop
pull_request:
jobs:
frontendTests:
runs-on: ubuntu-latest
name: JavaScript
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 8
- name: Install Node dependencies
run: npm install
- name: Run tests
run: npm run test

View File

@ -0,0 +1,23 @@
{
"problemMatcher": [
{
"owner": "phpcs",
"severity": "error",
"pattern": [
{
"regexp": "^<file name=\"(.*)\">$",
"file": 1
},
{
"regexp": "<error line=\"(\\d*)\" column=\"(\\d*)\" severity=\"(error|warning)\" message=\"(.*)\" source=\"(.*)(\"\\/>+)$",
"line": 1,
"column": 2,
"severity": 3,
"message": 4,
"code": 5,
"loop": true
}
]
}
]
}

View File

@ -8,6 +8,20 @@ on:
pull_request:
jobs:
frontendTests:
runs-on: ubuntu-latest
name: JavaScript
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 8
- name: Install Node dependencies
run: npm install
- name: Run tests
run: npm run test
phpUnitTests:
runs-on: ubuntu-latest
strategy:
@ -15,22 +29,79 @@ jobs:
matrix:
phpVersions: ['7.2', '7.3', '7.4']
fail-fast: false
name: PHP ${{ matrix.phpVersions }}
name: Unit Tests / PHP ${{ matrix.phpVersions }}
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install PHP
uses: shivammathur/setup-php@master
uses: shivammathur/setup-php@v1
with:
php-version: ${{ matrix.phpVersions }}
extension-csv: mbstring, intl, gd, xml, sqlite
extensions: mbstring, intl, gd, xml, sqlite
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Set Composer cache
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache Composer dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-suggest --no-scripts
- name: Run post-update Composer scripts
run: php artisan package:discover
- name: Reset October modules
run: |
php artisan october:util set build
php artisan package:discover
git reset --hard HEAD
composer dumpautoload
- name: Run Linting and Tests
run: |
./vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php .
./vendor/bin/phpunit --prepend ./vendor/october/rain/src/Support/helpers.php
duskTests:
runs-on: ubuntu-latest
name: Browser Tests
steps:
- name: Checkout changes
uses: actions/checkout@v1
- name: Install PHP
uses: shivammathur/setup-php@v1
with:
php-version: '7.3'
extensions: mbstring, intl, gd, xml, sqlite
- name: Setup problem matcher for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Set Composer cache
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache Composer dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-suggest --no-scripts
- name: Run post-update Composer scripts
run: php artisan package:discover
- name: Reset October modules
run: |
git reset --hard HEAD
composer dumpautoload
- name: Install Chrome driver
run: php artisan dusk:chrome-driver
- name: Start Chrome driver
run: ./vendor/laravel/dusk/bin/chromedriver-linux > /dev/null 2>&1 &
- name: Run Laravel Server
run: php artisan serve > /dev/null 2>&1 &
- name: Run Dusk Tests
run: php artisan dusk
- name: Upload Dusk Screenshots
uses: actions/upload-artifact@v1
if: failure()
with:
name: dusk-screenshots
path: ./tests/Browser/screenshots

View File

@ -49,16 +49,13 @@
"dms/phpunit-arraysubset-asserts": "^0.1.0",
"meyfa/phpunit-assert-gd": "^2.0",
"squizlabs/php_codesniffer": "3.*",
"jakub-onderka/php-parallel-lint": "^1.0"
"jakub-onderka/php-parallel-lint": "^1.0",
"laravel/dusk": "^5.8"
},
"autoload-dev": {
"classmap": [
"tests/concerns/InteractsWithAuthentication.php",
"tests/fixtures/backend/models/UserFixture.php",
"tests/TestCase.php",
"tests/UiTestCase.php",
"tests/PluginTestCase.php"
]
"psr-4": {
"October\\Core\\Tests\\": "tests/"
}
},
"scripts": {
"post-create-project-cmd": [
@ -92,6 +89,11 @@
"recurse": true,
"replace": false,
"merge-dev": false
},
"laravel": {
"dont-discover": [
"laravel/dusk"
]
}
}
}

30
config/dusk/app.php Normal file
View File

@ -0,0 +1,30 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://127.0.0.1:8000'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY', 'Gl8AHFqlhnzcpxeJGt0zLN5qe5ngPdiC'),
];

27
config/dusk/cms.php Normal file
View File

@ -0,0 +1,27 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Specifies the default CMS theme
|--------------------------------------------------------------------------
|
| This parameter value can be overridden by the CMS back-end settings.
|
*/
'activeTheme' => 'demo',
/*
|--------------------------------------------------------------------------
| Cross Site Request Forgery (CSRF) Protection
|--------------------------------------------------------------------------
|
| If the CSRF protection is enabled, all "postback" & AJAX requests are
| checked for a valid security token.
|
*/
'enableCsrfProtection' => false,
];

146
config/dusk/database.php Normal file
View File

@ -0,0 +1,146 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| PDO Fetch Style
|--------------------------------------------------------------------------
|
| By default, database results will be returned as instances of the PHP
| stdClass object; however, you may desire to retrieve records in an
| array format for simplicity. Here you can tweak the fetch style.
|
*/
'fetch' => PDO::FETCH_CLASS,
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', 'storage/dusk.sqlite'),
'prefix' => '',
],
'mysql' => [
'driver' => 'mysql',
'engine' => 'InnoDB',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'database'),
'username' => env('DB_USERNAME', ''),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'varcharmax' => 191,
],
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 5432),
'database' => env('DB_DATABASE', 'database'),
'username' => env('DB_USERNAME', ''),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 5432),
'database' => env('DB_DATABASE', 'database'),
'username' => env('DB_USERNAME', ''),
'password' => env('DB_PASSWORD', ''),
'prefix' => '',
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk have not actually be run in the databases.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer set of commands than a typical key-value systems
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'cluster' => false,
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', ''),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
],
/*
|--------------------------------------------------------------------------
| Use DB configuration for testing
|--------------------------------------------------------------------------
|
| When running plugin tests OctoberCMS by default uses SQLite in memory.
| You can override this behavior by setting `useConfigForTesting` to true.
|
| After that OctoberCMS will take DB parameters from the config.
| If file `/config/testing/database.php` exists, config will be read from it,
| but remember that when not specified it will use parameters specified in
| `/config/database.php`.
|
*/
'useConfigForTesting' => env('DB_USE_CONFIG_FOR_TESTING', false),
];

View File

@ -261,6 +261,15 @@ class ServiceProvider extends ModuleServiceProvider
$this->registerConsoleCommand('theme.list', 'System\Console\ThemeList');
$this->registerConsoleCommand('theme.use', 'System\Console\ThemeUse');
$this->registerConsoleCommand('theme.sync', 'System\Console\ThemeSync');
if (!App::isProduction() && class_exists('Laravel\Dusk\Dusk')) {
$this->registerConsoleCommand('dusk', 'System\Console\Dusk');
$this->registerConsoleCommand('dusk.fails', 'System\Console\DuskFails');
$this->commands([
\Laravel\Dusk\Console\ChromeDriverCommand::class,
]);
}
}
/*

View File

@ -0,0 +1,89 @@
<?php namespace System\Console;
use Laravel\Dusk\Console\DuskCommand as BaseDuskCommand;
class Dusk extends BaseDuskCommand
{
/**
* Setup the Dusk environment.
*
* @return void
*/
protected function setupDuskEnvironment()
{
if (file_exists(base_path($this->duskFile()))) {
if (!file_exists(base_path('.env'))) {
$this->stubEnvironment();
} elseif (file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) {
$this->backupEnvironment();
}
$this->refreshEnvironment();
}
$this->writeConfiguration();
$this->setupSignalHandler();
}
/**
* Restore the original environment.
*
* @return void
*/
protected function teardownDuskEnviroment()
{
$this->removeConfiguration();
if (
file_exists(base_path($this->duskFile()))
&& (
file_exists(base_path('.env.backup'))
|| file_exists(base_path('.env.blank'))
)
) {
$this->restoreEnvironment();
}
}
/**
* Stub a current environment file.
*
* @return void
*/
protected function stubEnvironment()
{
touch(base_path('.env.blank'));
copy(base_path($this->duskFile()), base_path('.env'));
}
/**
* Backup the current environment file.
*
* @return void
*/
protected function backupEnvironment()
{
copy(base_path('.env'), base_path('.env.backup'));
copy(base_path($this->duskFile()), base_path('.env'));
}
/**
* Restore the backed-up environment file.
*
* @return void
*/
protected function restoreEnvironment()
{
if (file_exists(base_path('.env.blank'))) {
unlink(base_path('.env'));
unlink(base_path('.env.blank'));
} else {
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
}
}
}

View File

@ -0,0 +1,31 @@
<?php namespace System\Console;
class DuskFails extends Dusk
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk:fails {--without-tty : Disable output to TTY}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the failing Dusk tests from the last run and stop on failure';
/**
* Get the array of arguments for running PHPUnit.
*
* @param array $options
* @return array
*/
protected function phpunitArguments($options)
{
return array_unique(array_merge(parent::phpunitArguments($options), [
'--cache-result', '--order-by=defects', '--stop-on-failure',
]));
}
}

34
phpunit.dusk.xml.dist Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="October CMS Browser Test Suite">
<directory suffix="Test.php">./tests/Browser/Backend</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./modules/</directory>
<exclude>
<file>./modules/backend/routes.php</file>
<file>./modules/cms/routes.php</file>
<file>./modules/system/routes.php</file>
<directory suffix=".php">./modules/backend/database</directory>
<directory suffix=".php">./modules/cms/database</directory>
<directory suffix=".php">./modules/system/database</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,41 @@
<?php namespace October\Core\Tests\Browser\Backend;
use Laravel\Dusk\Browser;
use October\Core\Tests\Browser\Pages\Backend\ForgotPassword;
use October\Core\Tests\Browser\Pages\Backend\Login;
class AuthTest extends \October\Core\Tests\BrowserTestCase
{
public function testSignInAndOut()
{
$this->browse(function (Browser $browser) {
$browser
->signInToBackend()
->click('@accountMenu')
->clickLink('Sign out');
$browser
->on(new Login);
});
}
public function testPasswordReset()
{
$this->browse(function (Browser $browser) {
$browser
->visit(new Login)
->pause(500)
->click('@forgotPasswordLink');
$browser
->on(new ForgotPassword)
->type('@loginField', 'admin')
->click('@submitButton');
$browser
->on(new Login)
->waitFor('.flash-message')
->assertSeeIn('.flash-message', 'Message sent to your email address');
});
}
}

View File

@ -0,0 +1,300 @@
<?php namespace October\Core\Tests\Browser\Backend\Cms;
use Laravel\Dusk\Browser;
use October\Core\Tests\Browser\Pages\Backend\Cms;
class TemplateTest extends \October\Core\Tests\BrowserTestCase
{
public function testPageTemplates()
{
$this->browse(function (Browser $browser) {
$browser
->signInToBackend()
->visit(new Cms)
->pause(200);
// Fix side panel, if necessary
if ($browser->hasClass('', 'side-panel-not-fixed')) {
$browser
->mouseover('@sideNav > li[data-menu-item="pages"]')
->waitFor('@sidePanel')
->mouseover('@sidePanel')
->waitFor('@sidePanelFixButton')
->click('@sidePanelFixButton');
}
// Add a new page
$browser
->click('form[data-template-type="page"] button[data-control="create-template"]')
->waitFor('#cms-master-tabs .tab-content .tab-pane');
$tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id');
$browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]');
$this->assertEquals('New page', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]'));
$browser
->type('input[name="settings[title]"]', 'Functional Test Page')
->pause(100)
// Check that slug values are working
->assertInputValue('input[name="settings[url]"]', '/functional-test-page')
->assertInputValue('input[name="fileName"]', 'functional-test-page')
->clear('input[name="settings[url]"]')
->type('input[name="settings[url]"]', '/xxx/functional/test/page')
->clear('input[name="fileName"]')
->type('input[name="fileName"]', 'xxx_functional_test_page.htm')
// Check that slug values have not been re-added after manual entry
->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page')
->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm');
// Save the new page
$browser
->click('a[data-request="onSave"]')
->waitFor('.flash-message')
->assertSeeIn('.flash-message', 'Template saved.');
$this->assertEquals(
'Functional Test Page',
$browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title')
);
// Close the tab
$browser
->click('li[data-tab-id^="page-"][data-tab-id$="-xxx_functional_test_page.htm"] span.tab-close')
->pause(100)
->assertMissing('#cms-master-tabs .tab-content .tab-pane');
// Re-open the page
$browser
->click('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"] a')
->waitFor('#cms-master-tabs .tab-content .tab-pane')
// Check that saved details are still there
->assertInputValue('input[name="settings[title]"]', 'Functional Test Page')
->assertInputValue('input[name="settings[url]"]', '/xxx/functional/test/page')
->assertInputValue('input[name="fileName"]', 'xxx_functional_test_page.htm');
// Delete the page
$browser
->click('button[data-request="onDelete"]')
->waitFor('.sweet-alert.showSweetAlert.visible')
->pause(300)
->click('.sweet-alert.showSweetAlert.visible button.confirm')
->waitUntilMissing('div#TemplateList-pageList-template-list li[data-item-path="xxx_functional_test_page.htm"]');
});
}
public function testPartialTemplates()
{
$this->browse(function (Browser $browser) {
$browser
->signInToBackend()
->visit(new Cms)
->pause(200);
// Fix side panel, if necessary
if ($browser->hasClass('', 'side-panel-not-fixed')) {
$browser
->mouseover('@sideNav > li[data-menu-item="pages"]')
->waitFor('@sidePanel')
->mouseover('@sidePanel')
->waitFor('@sidePanelFixButton')
->click('@sidePanelFixButton');
}
$browser
->click('@sideNav > li[data-menu-item="partials"] a');
// Add a new partial
$browser
->click('form[data-template-type="partial"] button[data-control="create-template"]')
->waitFor('#cms-master-tabs .tab-content .tab-pane');
$tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id');
$browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]');
$this->assertEquals('New partial', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]'));
$browser
->type('input[name="fileName"]', 'xxx_functional_test_partial')
->type('input[name="settings[description]"]', 'Test Partial');
// Save the new partial
$browser
->click('a[data-request="onSave"]')
->waitFor('.flash-message')
->assertSeeIn('.flash-message', 'Template saved.');
$this->assertEquals(
'xxx_functional_test_partial',
$browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title')
);
// Close the tab
$browser
->click('li[data-tab-id^="partial-"][data-tab-id$="-xxx_functional_test_partial.htm"] span.tab-close')
->pause(100)
->assertMissing('#cms-master-tabs .tab-content .tab-pane');
// Re-open the partial
$browser
->click('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"] a')
->waitFor('#cms-master-tabs .tab-content .tab-pane')
// Check that saved details are still there
->assertInputValue('input[name="fileName"]', 'xxx_functional_test_partial.htm')
->assertInputValue('input[name="settings[description]"]', 'Test Partial');
// Delete the partial
$browser
->click('button[data-request="onDelete"]')
->waitFor('.sweet-alert.showSweetAlert.visible')
->pause(300)
->click('.sweet-alert.showSweetAlert.visible button.confirm')
->waitUntilMissing('div#TemplateList-partialList-template-list li[data-item-path="xxx_functional_test_partial.htm"]');
});
}
public function testLayoutTemplates()
{
$this->browse(function (Browser $browser) {
$browser
->signInToBackend()
->visit(new Cms)
->pause(200);
// Fix side panel, if necessary
if ($browser->hasClass('', 'side-panel-not-fixed')) {
$browser
->mouseover('@sideNav > li[data-menu-item="pages"]')
->waitFor('@sidePanel')
->mouseover('@sidePanel')
->waitFor('@sidePanelFixButton')
->click('@sidePanelFixButton');
}
$browser
->click('@sideNav > li[data-menu-item="layouts"] a');
// Add a new layout
$browser
->click('form[data-template-type="layout"] button[data-control="create-template"]')
->waitFor('#cms-master-tabs .tab-content .tab-pane');
$tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id');
$browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]');
$this->assertEquals('New layout', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]'));
$browser
->type('input[name="fileName"]', 'xxx_functional_test_layout')
->type('input[name="settings[description]"]', 'Test Layout');
// Save the new layout
$browser
->click('a[data-request="onSave"]')
->waitFor('.flash-message')
->assertSeeIn('.flash-message', 'Template saved.');
$this->assertEquals(
'xxx_functional_test_layout',
$browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title')
);
// Close the tab
$browser
->click('li[data-tab-id^="layout-"][data-tab-id$="-xxx_functional_test_layout.htm"] span.tab-close')
->pause(100)
->assertMissing('#cms-master-tabs .tab-content .tab-pane');
// Re-open the partial
$browser
->click('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"] a')
->waitFor('#cms-master-tabs .tab-content .tab-pane')
// Check that saved details are still there
->assertInputValue('input[name="fileName"]', 'xxx_functional_test_layout.htm')
->assertInputValue('input[name="settings[description]"]', 'Test Layout');
// Delete the partial
$browser
->click('button[data-request="onDelete"]')
->waitFor('.sweet-alert.showSweetAlert.visible')
->pause(300)
->click('.sweet-alert.showSweetAlert.visible button.confirm')
->waitUntilMissing('div#TemplateList-layoutList-template-list li[data-item-path="xxx_functional_test_layout.htm"]');
});
}
public function testContentTemplates()
{
$this->browse(function (Browser $browser) {
$browser
->signInToBackend()
->visit(new Cms)
->pause(200);
// Fix side panel, if necessary
if ($browser->hasClass('', 'side-panel-not-fixed')) {
$browser
->mouseover('@sideNav > li[data-menu-item="pages"]')
->waitFor('@sidePanel')
->mouseover('@sidePanel')
->waitFor('@sidePanelFixButton')
->click('@sidePanelFixButton');
}
$browser
->click('@sideNav > li[data-menu-item="content"] a');
// Add a new content file
$browser
->click('form[data-template-type="content"] button[data-control="create-template"]')
->waitFor('#cms-master-tabs .tab-content .tab-pane');
$tabId = $browser->attribute('#cms-master-tabs .tab-content .tab-pane', 'id');
$browser->assertPresent('a[data-toggle="tab"][data-target="#' . $tabId . '"]');
$this->assertStringContainsString('content', $browser->text('a[data-toggle="tab"][data-target="#' . $tabId . '"]'));
$browser
->type('input[name="fileName"]', 'xxx_functional_test_content.txt');
// Save the new content file
$browser
->click('a[data-request="onSave"]')
->waitFor('.flash-message')
->assertSeeIn('.flash-message', 'Template saved.');
$this->assertEquals(
'xxx_functional_test_content.txt',
$browser->attribute('a[data-toggle="tab"][data-target="#' . $tabId . '"] span.title', 'title')
);
// Close the tab
$browser
->click('li[data-tab-id^="content-"][data-tab-id$="-xxx_functional_test_content.txt"] span.tab-close')
->pause(100)
->assertMissing('#cms-master-tabs .tab-content .tab-pane');
// Re-open the partial
$browser
->click('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"] a')
->waitFor('#cms-master-tabs .tab-content .tab-pane')
// Check that saved details are still there
->assertInputValue('input[name="fileName"]', 'xxx_functional_test_content.txt');
// Delete the partial
$browser
->click('button[data-request="onDelete"]')
->waitFor('.sweet-alert.showSweetAlert.visible')
->pause(300)
->click('.sweet-alert.showSweetAlert.visible button.confirm')
->waitUntilMissing('div#TemplateList-contentList-template-list li[data-item-path="xxx_functional_test_content.txt"]');
});
}
}

View File

@ -0,0 +1,32 @@
<?php namespace October\Core\Tests\Browser\Pages\Backend;
use Laravel\Dusk\Browser;
use October\Core\Tests\Browser\Pages\BackendPage;
class Cms extends BackendPage
{
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/backend/cms';
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser
->assertTitleContains('CMS |')
->assertPresent('@mainMenu')
->assertPresent('@sideNav')
->assertPresent('@accountMenu');
}
}

View File

@ -0,0 +1,33 @@
<?php namespace October\Core\Tests\Browser\Pages\Backend;
use Laravel\Dusk\Browser;
use October\Core\Tests\Browser\Pages\BackendPage;
class Dashboard extends BackendPage
{
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/backend';
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser
->assertTitleContains('Dashboard |')
->assertPresent('@mainMenu')
->assertPresent('@accountMenu')
->waitFor('.report-widget')
->assertSee('Welcome');
}
}

View File

@ -0,0 +1,48 @@
<?php namespace October\Core\Tests\Browser\Pages\Backend;
use Laravel\Dusk\Browser;
use October\Core\Tests\Browser\Pages\Page;
class ForgotPassword extends Page
{
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/backend/backend/auth/restore';
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser
->assertTitle('Administration Area')
->assertPresent('@loginField')
->assertMissing('input[name="password"]')
->assertPresent('@submitButton')
->assertPresent('@cancelLink')
->assertSeeIn('@submitButton', 'Restore');
}
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public function elements()
{
return [
'@loginField' => 'input[name="login"]',
'@submitButton' => 'button[type="submit"]',
'@cancelLink' => 'p.forgot-password > a',
];
}
}

View File

@ -0,0 +1,49 @@
<?php namespace October\Core\Tests\Browser\Pages\Backend;
use Laravel\Dusk\Browser;
use October\Core\Tests\Browser\Pages\Page;
class Login extends Page
{
/**
* Get the URL for the page.
*
* @return string
*/
public function url()
{
return '/backend/backend/auth/signin';
}
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
$browser
->assertTitle('Administration Area')
->assertPresent('@loginField')
->assertPresent('@passwordField')
->assertPresent('@submitButton')
->assertPresent('@forgotPasswordLink')
->assertSeeIn('@submitButton', 'Login');
}
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public function elements()
{
return [
'@loginField' => 'input[name="login"]',
'@passwordField' => 'input[name="password"]',
'@submitButton' => 'button[type="submit"]',
'@forgotPasswordLink' => 'p.forgot-password > a',
];
}
}

View File

@ -0,0 +1,21 @@
<?php namespace October\Core\Tests\Browser\Pages;
abstract class BackendPage extends Page
{
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements()
{
return [
'@mainMenu' => '#layout-mainmenu',
'@accountMenu' => '#layout-mainmenu .mainmenu-account > a',
'@sideNav' => '#layout-sidenav > ul',
'@sidePanel' => '#layout-side-panel',
'@sidePanelFixButton' => '#layout-side-panel a.fix-button',
];
}
}

View File

@ -0,0 +1,16 @@
<?php namespace October\Core\Tests\Browser\Pages;
use Laravel\Dusk\Page as BasePage;
abstract class Page extends BasePage
{
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements()
{
return [];
}
}

2
tests/Browser/console/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
tests/Browser/screenshots/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

147
tests/BrowserTestCase.php Normal file
View File

@ -0,0 +1,147 @@
<?php namespace October\Core\Tests;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Laravel\Dusk\Browser;
use Laravel\Dusk\TestCase as DuskTestCase;
use October\Core\Tests\Browser\Pages\Backend\Dashboard;
use October\Core\Tests\Browser\Pages\Backend\Login;
use October\Core\Tests\Concerns\CreatesApplication;
use October\Core\Tests\Concerns\InteractsWithAuthentication;
use October\Core\Tests\Concerns\RunsMigrations;
use October\Core\Tests\Concerns\TestsPlugins;
abstract class BrowserTestCase extends DuskTestCase
{
use CreatesApplication;
use InteractsWithAuthentication;
use RunsMigrations;
use TestsPlugins;
/**
* Prepare for Dusk test execution.
*
* @beforeClass
* @return void
*/
public static function prepare()
{
static::startChromeDriver();
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--headless',
'--window-size=1920,1080',
]);
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
}
public function setUp(): void
{
$this->resetManagers();
parent::setUp();
// Ensure system is up to date
if ($this->usingTestDatabase) {
$this->runOctoberUpCommand();
}
// Detect a plugin and autoload it, if necessary
$this->detectPlugin();
// Disable mailer
\Mail::pretend();
Browser::$baseUrl = $this->baseUrl();
Browser::$storeScreenshotsAt = base_path('tests/Browser/screenshots');
Browser::$storeConsoleLogAt = base_path('tests/Browser/console');
Browser::$userResolver = function () {
return $this->user();
};
$this->setupMacros();
}
public function tearDown(): void
{
if ($this->usingTestDatabase && isset($this->testDatabasePath)) {
unlink($this->testDatabasePath);
}
parent::tearDown();
}
/**
* Defines October macros for use in browser tests
*
* @return void
*/
protected function setupMacros()
{
/**
* Signs the user into the backend
*/
Browser::macro('signInToBackend', function (string $username = null, string $password = null) {
$username = $username ?? env('DUSK_ADMIN_USER', 'admin');
$password = $password ?? env('DUSK_ADMIN_PASS', 'admin1234');
$this
->visit(new Login)
->pause(500)
->type('@loginField', $username)
->type('@passwordField', $password)
->click('@submitButton');
$this->
on(new Dashboard);
return $this;
});
Browser::macro('hasClass', function (string $selector, string $class) {
$classes = preg_split('/\s+/', $this->attribute($selector, 'class'), -1, PREG_SPLIT_NO_EMPTY);
if (empty($classes)) {
return false;
}
return in_array($class, $classes);
});
}
/**
* Similar to the native getConfirmation() function
*/
protected function getSweetConfirmation($expectedText = null, $clickOk = true)
{
$this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]");
if ($expectedText) {
$this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText);
}
$this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK");
if ($clickOk) {
$this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]");
}
}
}

View File

@ -0,0 +1,98 @@
<?php namespace October\Core\Tests\Concerns;
use Config;
use Backend\Classes\AuthManager;
trait CreatesApplication
{
/**
* Determines if a test SQLite database is being used
*
* @var boolean
*/
protected $usingTestDatabase = false;
/**
* The test SQLite database in use
*
* @var string
*/
protected $testDatabasePath;
/**
* Creates the application.
*
* @return \Illuminate\Foundation\Application
*/
public function createApplication()
{
$app = require __DIR__ . '/../../bootstrap/app.php';
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
$app['cache']->setDefaultDriver('array');
$app->setLocale('en');
$app->singleton('auth', function ($app) {
$app['auth.loaded'] = true;
return AuthManager::instance();
});
// Use test database configuration, unless overriden
$dbConnection = Config::get('database.default', 'sqlite');
$dbConnections = [
$dbConnection => Config::get('database.connections.' . $dbConnection, [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
])
];
if (env('APP_ENV') === 'testing' && !Config::get('database.useConfigForTesting', false)) {
$this->usingTestDatabase = true;
$dbConnection = 'sqlite';
$dbConnections = [
'sqlite' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
];
} elseif (env('APP_ENV') === 'dusk' && !Config::get('database.useConfigForTesting', false)) {
$this->usingTestDatabase = true;
$dbConnection = 'sqlite';
$dbConnections = [
'sqlite' => [
'driver' => 'sqlite',
'database' => 'storage/dusk.sqlite',
'prefix' => '',
],
];
// Ensure a fresh copy of the SQLite database is made
$this->testDatabasePath = base_path('storage/dusk.sqlite');
if (file_exists($this->testDatabasePath)) {
unlink($this->testDatabasePath);
}
touch($this->testDatabasePath);
}
$app['config']->set('database.default', $dbConnection);
$app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]);
/**
* Prevent mail from being sent out
*/
$app['config']->set('mail.driver', 'array');
/**
* Modify the plugin path away from the test context
*/
$app->setPluginsPath(realpath(base_path() . Config::get('cms.pluginsPath')));
return $app;
}
}

View File

@ -1,6 +1,4 @@
<?php
namespace October\Tests\Concerns;
<?php namespace October\Core\Tests\Concerns;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;

View File

@ -0,0 +1,14 @@
<?php namespace October\Core\Tests\Concerns;
trait RunsMigrations
{
protected function runOctoberUpCommand()
{
\Artisan::call('october:up');
}
protected function runOctoberDownCommand()
{
\Artisan::call('october:down --force');
}
}

View File

@ -0,0 +1,108 @@
<?php namespace October\Core\Tests\Concerns;
use System\Classes\UpdateManager;
use System\Classes\PluginManager;
trait TestsPlugins
{
/**
* @var array Cache for storing which plugins have been loaded
* and refreshed.
*/
protected $pluginTestCaseLoadedPlugins = [];
public function resetManagers(): void
{
PluginManager::forgetInstance();
UpdateManager::forgetInstance();
}
/**
* Detects the current plugin based on the namespace, when running tests within a plugin.
*
* @return void
*/
public function detectPlugin(): void
{
$this->pluginTestCaseLoadedPlugins = [];
$pluginCode = $this->guessPluginCodeFromTest();
if ($pluginCode !== false) {
$this->runPluginRefreshCommand($pluginCode, false);
}
}
/**
* Locates the plugin code based on the test file location.
*
* @return string|bool
*/
protected function guessPluginCodeFromTest()
{
$reflect = new \ReflectionClass($this);
$path = $reflect->getFilename();
$basePath = $this->app->pluginsPath();
$result = false;
if (strpos($path, $basePath) === 0) {
$result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/');
$result = implode('.', array_slice(explode('/', $result), 0, 2));
}
return $result;
}
/**
* Runs a refresh command on a plugin.
*
* Since the test environment has loaded all the test plugins
* natively, this method will ensure the desired plugin is
* loaded in the system before proceeding to migrate it.
*
* @return void
*/
protected function runPluginRefreshCommand($code, $throwException = true): void
{
if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) {
if (!$throwException) {
return;
}
throw new \Exception(sprintf('Invalid plugin code: "%s"', $code));
}
$manager = PluginManager::instance();
$plugin = $manager->findByIdentifier($code);
// First time seeing this plugin, load it up
if (!$plugin) {
$namespace = '\\'.str_replace('.', '\\', strtolower($code));
$path = array_get($manager->getPluginNamespaces(), $namespace);
if (!$path) {
if (!$throwException) {
return;
}
throw new \Exception(sprintf('Unable to find plugin with code: "%s"', $code));
}
$plugin = $manager->loadPlugin($namespace, $path);
}
// Spin over dependencies and refresh them too
$this->pluginTestCaseLoadedPlugins[$code] = $plugin;
if (!empty($plugin->require)) {
foreach ((array) $plugin->require as $dependency) {
if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) {
continue;
}
$this->runPluginRefreshCommand($dependency);
}
}
// Execute the command
\Artisan::call('plugin:refresh', ['name' => $code]);
}
}

View File

@ -1,67 +1,17 @@
<?php
<?php namespace October\Core\Tests;
use Backend\Classes\AuthManager;
use System\Classes\UpdateManager;
use System\Classes\PluginManager;
use October\Rain\Database\Model as ActiveRecord;
use October\Tests\Concerns\InteractsWithAuthentication;
use October\Core\Tests\Concerns\CreatesApplication;
use October\Core\Tests\Concerns\InteractsWithAuthentication;
use October\Core\Tests\Concerns\RunsMigrations;
use October\Core\Tests\Concerns\TestsPlugins;
abstract class PluginTestCase extends TestCase
{
use CreatesApplication;
use InteractsWithAuthentication;
/**
* @var array Cache for storing which plugins have been loaded
* and refreshed.
*/
protected $pluginTestCaseLoadedPlugins = [];
/**
* Creates the application.
* @return Symfony\Component\HttpKernel\HttpKernelInterface
*/
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
$app['cache']->setDefaultDriver('array');
$app->setLocale('en');
$app->singleton('auth', function ($app) {
$app['auth.loaded'] = true;
return AuthManager::instance();
});
/*
* Store database in memory by default, if not specified otherwise
*/
$dbConnection = 'sqlite';
$dbConnections = [];
$dbConnections['sqlite'] = [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => ''
];
if (env('APP_ENV') === 'testing' && Config::get('database.useConfigForTesting', false)) {
$dbConnection = Config::get('database.default', 'sqlite');
$dbConnections[$dbConnection] = Config::get('database.connections' . $dbConnection, $dbConnections['sqlite']);
}
$app['config']->set('database.default', $dbConnection);
$app['config']->set('database.connections.' . $dbConnection, $dbConnections[$dbConnection]);
/*
* Modify the plugin path away from the test context
*/
$app->setPluginsPath(realpath(base_path().Config::get('cms.pluginsPath')));
return $app;
}
use RunsMigrations;
use TestsPlugins;
/**
* Perform test case set up.
@ -69,36 +19,21 @@ abstract class PluginTestCase extends TestCase
*/
public function setUp() : void
{
/*
* Force reload of October singletons
*/
PluginManager::forgetInstance();
UpdateManager::forgetInstance();
$this->resetManagers();
/*
* Create application instance
*/
// Create application
parent::setUp();
/*
* Ensure system is up to date
*/
$this->runOctoberUpCommand();
/*
* Detect plugin from test and autoload it
*/
$this->pluginTestCaseLoadedPlugins = [];
$pluginCode = $this->guessPluginCodeFromTest();
if ($pluginCode !== false) {
$this->runPluginRefreshCommand($pluginCode, false);
// Ensure system is up to date
if ($this->usingTestDatabase) {
$this->runOctoberUpCommand();
}
/*
* Disable mailer
*/
Mail::pretend();
// Detect a plugin and autoload it, if necessary
$this->detectPlugin();
// Disable mailer
\Mail::pretend();
}
/**
@ -108,88 +43,8 @@ abstract class PluginTestCase extends TestCase
public function tearDown() : void
{
$this->flushModelEventListeners();
parent::tearDown();
unset($this->app);
}
/**
* Migrate database using october:up command.
* @return void
*/
protected function runOctoberUpCommand()
{
Artisan::call('october:up');
}
/**
* Since the test environment has loaded all the test plugins
* natively, this method will ensure the desired plugin is
* loaded in the system before proceeding to migrate it.
* @return void
*/
protected function runPluginRefreshCommand($code, $throwException = true)
{
if (!preg_match('/^[\w+]*\.[\w+]*$/', $code)) {
if (!$throwException) {
return;
}
throw new Exception(sprintf('Invalid plugin code: "%s"', $code));
}
$manager = PluginManager::instance();
$plugin = $manager->findByIdentifier($code);
/*
* First time seeing this plugin, load it up
*/
if (!$plugin) {
$namespace = '\\'.str_replace('.', '\\', strtolower($code));
$path = array_get($manager->getPluginNamespaces(), $namespace);
if (!$path) {
if (!$throwException) {
return;
}
throw new Exception(sprintf('Unable to find plugin with code: "%s"', $code));
}
$plugin = $manager->loadPlugin($namespace, $path);
}
/*
* Spin over dependencies and refresh them too
*/
$this->pluginTestCaseLoadedPlugins[$code] = $plugin;
if (!empty($plugin->require)) {
foreach ((array) $plugin->require as $dependency) {
if (isset($this->pluginTestCaseLoadedPlugins[$dependency])) {
continue;
}
$this->runPluginRefreshCommand($dependency);
}
}
/*
* Execute the command
*/
Artisan::call('plugin:refresh', ['name' => $code]);
}
/**
* Returns a plugin object from its code, useful for registering events, etc.
* @return PluginBase
*/
protected function getPluginObject($code = null)
{
if ($code === null) {
$code = $this->guessPluginCodeFromTest();
}
if (isset($this->pluginTestCaseLoadedPlugins[$code])) {
return $this->pluginTestCaseLoadedPlugins[$code];
}
}
/**
@ -205,7 +60,7 @@ abstract class PluginTestCase extends TestCase
continue;
}
$reflectClass = new ReflectionClass($class);
$reflectClass = new \ReflectionClass($class);
if (
!$reflectClass->isInstantiable() ||
!$reflectClass->isSubclassOf('October\Rain\Database\Model') ||
@ -219,24 +74,4 @@ abstract class PluginTestCase extends TestCase
ActiveRecord::flushEventListeners();
}
/**
* Locates the plugin code based on the test file location.
* @return string|bool
*/
protected function guessPluginCodeFromTest()
{
$reflect = new ReflectionClass($this);
$path = $reflect->getFilename();
$basePath = $this->app->pluginsPath();
$result = false;
if (strpos($path, $basePath) === 0) {
$result = ltrim(str_replace('\\', '/', substr($path, strlen($basePath))), '/');
$result = implode('.', array_slice(explode('/', $result), 0, 2));
}
return $result;
}
}

View File

@ -1,26 +1,143 @@
# Plugin testing
# Testing
Plugin unit tests can be performed by running `phpunit` in the base plugin directory.
October CMS has a suite of tools available for running automated tests on your October instance and plugins. To run tests, you must ensure that you have PHPUnit installed and can run the `phpunit` command from a command-line interface.
### Creating plugin tests
---
Plugins can be tested by creating a file called `phpunit.xml` in the base directory with the following content, for example, in a file **/plugins/acme/blog/phpunit.xml**:
- [System Tests](#system-tests)
- [Unit Tests](#unit-tests)
- [Using a Custom Database Engine](#custom-database-engine)
- [Browser Tests](#browser-tests)
- [Testing Environment for Browser Tests](#testing-environment)
- [JavaScript Tests](#javascript-tests)
- [Creating Tests for Plugins](#creating-plugin-tests)
- [Unit Tests](#plugin-unit-tests)
- [Browser Tests](#plugin-browser-tests)
---
<a name="system-tests"></a>
## System Tests
The system tests cover the tests that analyse the core functionality of October CMS. To run these tests, we recommend that you use Git to checkout a copy of the development version of October CMS and use Composer to install all necessary dependencies.
You can do this on command-line by simply running the following:
```bash
git checkout git@github.com:octobercms/october.git
cd october
composer install
```
<a name="unit-tests"></a>
### Unit Tests
The unit tests in October CMS can be found in the **tests/unit** folder and are executed through PHPUnit. You can run the tests by running the following command in the root folder of the October CMS installation:
```bash
./vendor/bin/phpunit
```
This will run tests for both October CMS and the Rain library. The Rain library tests can be found in the **vendor/october/rain/tests** folder.
Note that unit tests run in a special environment called `testing`. You may configure this environment by adding or modifying the configuration files in the **config/testing/** directory.
<a name="custom-database-engine"></a>
#### Using a Custom Database Engine
By default, OctoberCMS uses SQLite stored in memory for the `testing` environment. If you wish to override this with your own database configuration, set the `useConfigForTesting` config to `true` in your `/config/database.php` file.
When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`.
You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken.
<a name="browser-tests"></a>
### Browser Tests
Browsers tests are a more flexible type of automated test that run directly in a web browser. October CMS leverages the [Laravel Dusk](https://laravel.com/docs/6.x/dusk) framework to run these tests, which in turn uses Google Chrome and a ChromeDriver install to run the tests.
Running the browser tests will require Google Chrome to be installed on your machine. Once this is done, you may prepare your installation for running Browser tests by running the following in your project root folder:
```bash
php artisan dusk:chrome-driver
```
> **Note:** It is possible to use other browsers, or a standalone Selenium server, if you wish. Please see the [Laravel Dusk documentation](https://laravel.com/docs/6.x/dusk#using-other-browsers) for more information.
Once installed, you may run the browsers test by simply running the following command in the root folder of the October CMS installation:
```bash
php artisan dusk
```
If you have previously run the browser tests and want to re-run only the tests that failed, you may use this shortcut to run just the failed tests:
```bash
php artisan dusk:fails
```
Note that your October CMS installation must be web-accessible in order to run the browser tests. Please review the next section on setting up the testing environment.
<a name="testing-environment"></a>
#### Testing Environment for Browser Tests
The Browser tests in October CMS are, by default, set up to run within a special testing environment called `dusk`. This is configured to run October CMS via the inbuilt PHP web server, using an SQLite database to store the database temporarily.
You may start this web server before running the browser tests simply by running the following:
```bash
php artisan serve
```
This environment is configured in two places: the **.env.dusk** file available in the project root, and within the **config/dusk/** folder. You may modify either of these configuration files in order to configure the testing environment to your requirements.
When the browser tests are started, the **.env** file in your project (if any) is subtituted with **.env.dusk** for the duration of the tests. Once the tests end, the **.env** file is restored to its original content.
> **Note:** The system browser tests will need to be authenticated with a superuser-level user to run correctly. If you are using the default environment, this will happen automatically.<br><br>If you are custom settings however, you may need to provide authentication information to Dusk in order for it to run. You can specify the environment variables `DUSK_ADMIN_USER` and `DUSK_ADMIN_PASS` to use specific authentication credentials during testing. These are available in the `.env.dusk` file.
<a name="javascript-tests"></a>
### JavaScript Tests
In addition to the PHP-based tests above, we also have a suite of unit tests for our JavaScript libraries and functions. These run on an NodeJS-based environment, so you will need to [download and install](https://nodejs.org/en/download/) NodeJS and NPM in order to install the tools required for these tests.
Once installed, you may install the tools by running the following in the browser root:
```bash
npm install
```
Then, you may run the following command to run the JavaScript tests:
```bash
npm run test
```
<a name="creating-plugin-tests"></a>
## Creating Tests for Plugins
October CMS has made it easy for plugin developers to create unit and browser tests for their plugins.
Please read the sections below in order to configure your plugin for your required types of testing.
<a name="plugin-unit-tests"></a>
### Unit Tests
To allow unit testing in your plugin, you must first create a file called `phpunit.xml` in the plugin base directory with the following content - for example, in a file **/plugins/acme/blog/phpunit.xml**:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="../../../tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="../../../tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Plugin Unit Test Suite">
<directory>./tests</directory>
<directory>./tests/unit</directory>
</testsuite>
</testsuites>
<php>
@ -30,29 +147,53 @@ Plugins can be tested by creating a file called `phpunit.xml` in the base direct
</php>
</phpunit>
Then a **tests/** directory can be created to contain the test classes. The file structure should mimic the base directory with classes having a `Test` suffix. Using a namespace for the class is also recommended.
Then you may create a **tests/unit/** directory to contain the unit test classes.
<?php namespace Acme\Blog\Tests\Models;
Each unit test class file must match the following guidelines:
use Acme\Blog\Models\Post;
use PluginTestCase;
- Each file can be any number of folder levels deep, but must end with `Test.php`.
- Each test class must have a namespace that follows both your plugin name as well as the folder location of your test.
- Each test class must extend the `October\Core\Tests\PluginTestCase` class.
class PostTest extends PluginTestCase
For example, if you have a plugin `Acme.Blog` that contained a `Post` model that you wanted to test, you would create the test class file in **tests/unit/models/PostTest.php**, with the test class code containing the following:
```php
<?php namespace Acme\Blog\Tests\Unit\Models;
use Acme\Blog\Models\Post;
use October\Core\Tests\PluginTestCase;
class PostTest extends PluginTestCase
{
public function testCreateFirstPost()
{
public function testCreateFirstPost()
{
$post = Post::create(['title' => 'Hi!']);
$this->assertEquals(1, $post->id);
}
$post = Post::create(['title' => 'Hi!']);
$this->assertEquals(1, $post->id);
}
}
```
The test class should extend the base class `PluginTestCase` and this is a special class that will set up the October database stored in memory, as part of the `setUp` method. It will also refresh the plugin being tested, along with any of the defined dependencies in the plugin registration file. This is the equivalent of running the following before each test:
The `October\Core\Tests\PluginTestCase` class takes care of ensuring that you have a clean database for each test, in order to run each test in isolation. If you need to run some code before, or after each test, you may overwrite the `setUp` and `tearDown` methods in your class. It is important, however, that you allow the parent methods to run too.
php artisan october:up
php artisan plugin:refresh Acme.Blog
[php artisan plugin:refresh <dependency>, ...]
```php
public function setUp(): void
{
parent::setUp();
> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests. Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation.
// Load necessary model fixture
$this->postFixture = PostFixture::create(['title' => 'Test Post']);
}
public function tearDown(): void
{
// Remove model fixtur
unset($this->postFixture);
parent::tearDown();
}
```
> **Note:** If your plugin uses [configuration files](../plugin/settings#file-configuration), then you will need to run `System\Classes\PluginManager::instance()->registerAll(true);` in the `setUp` method of your tests.<br><br>Below is an example of a base test case class that should be used if you need to test your plugin working with other plugins instead of in isolation.
use System\Classes\PluginManager;
@ -84,51 +225,51 @@ The test class should extend the base class `PluginTestCase` and this is a speci
}
}
#### Changing database engine for plugins tests
To run the unit tests for your plugin, simply go to the base folder for your plugin, and run the following command:
By default OctoberCMS uses SQLite stored in memory for the plugin testing environment. If you want to override the default behavior set the `useConfigForTesting` config to `true` in your `/config/database.php` file. When the `APP_ENV` is `testing` and the `useConfigForTesting` is `true` database parameters will be taken from `/config/database.php`.
```bash
../../../vendor/bin/phpunit
```
You can override the `/config/database.php` file by creating `/config/testing/database.php`. In this case variables from the latter file will be taken.
This will execute PHPUnit in the context of your plugin.
## System testing
<a name="plugin-browser-tests"></a>
### Browsers Tests
To perform unit testing on the core October files, you should download a development copy using composer or cloning the git repo. This will ensure you have the `tests/` directory.
Browser tests for plugins can be set up in much the same way as unit tests, with a small number of differences.
### Unit tests
Browsers tests require their own PHPUnit XML configuration file. You should create a `phpunit.dusk.xml` file in your project base directory with the following content:
Unit tests can be performed by running `phpunit` in the root directory or inside `/tests/unit`.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="../../../tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Plugin Browser Test Suite">
<directory>./tests/browser</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="dusk"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>
### Functional tests
Browser tests should be separate from unit tests - you can instead store browser tests within the **tests/browser/** directory.
Functional tests can be performed by running `phpunit` in the `/tests/functional` directory. Ensure the following configuration is met:
Browser test files follow the same rules as unit test files, however, instead of extending the `October\Core\Tests\PluginTestCase` class, they should instead extend the `October\Core\Tests\BrowserTestCase` class.
- Active theme is `demo`
- Language preference is `en`
To run the plugin browser tests, you must run the following command within the root folder of your *October CMS install*, not the plugin:
#### Selenium set up
1. Download latest Java SE from http://java.sun.com/ and install
1. Download a distribution archive of [Selenium Server](http://seleniumhq.org/download/).
1. Unzip the distribution archive and copy selenium-server-standalone-2.42.2.jar (check the version suffix) to /usr/local/bin, for instance.
1. Start the Selenium Server server by running `java -jar /usr/local/bin/selenium-server-standalone-2.42.2.jar`.
#### Selenium configuration
Create a new file `selenium.php` in the root directory, add the following content:
<?php
// Selenium server details
define('TEST_SELENIUM_HOST', '127.0.0.1');
define('TEST_SELENIUM_PORT', 4444);
define('TEST_SELENIUM_BROWSER', '*firefox');
// Back-end URL
define('TEST_SELENIUM_URL', 'http://localhost/backend/');
// Active Theme
define('TEST_SELENIUM_THEME', 'demo');
// Back-end credentials
define('TEST_SELENIUM_USER', 'admin');
define('TEST_SELENIUM_PASS', 'admin');
```bash
php artisan dusk -c plugins/acme/test/phpunit.dusk.xml
```

View File

@ -1,5 +1,6 @@
<?php
class TestCase extends Illuminate\Foundation\Testing\TestCase
<?php namespace October\Core\Tests;
abstract class TestCase extends \Illuminate\Foundation\Testing\TestCase
{
/**
* Creates the application.
@ -21,11 +22,10 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$class = new \ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
@ -34,7 +34,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
public static function getProtectedProperty($object, $name)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$class = new \ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
@ -43,7 +43,7 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
public static function setProtectedProperty($object, $name, $value)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$class = new \ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->setValue($object, $value);

View File

@ -1,111 +0,0 @@
<?php
class UiTestCase extends PHPUnit\Extensions\Selenium2TestCase
{
protected function setUp()
{
/*
* Look for selenium configuration
*/
if (file_exists($seleniumEnv = __DIR__.'/../selenium.php')) {
require_once $seleniumEnv;
}
/*
* Configure selenium
*/
if (!defined('TEST_SELENIUM_URL')) {
return $this->markTestSkipped('Selenium skipped');
}
if (defined('TEST_SELENIUM_HOST')) {
$this->setHost(TEST_SELENIUM_HOST);
}
if (defined('TEST_SELENIUM_PORT')) {
$this->setPort(TEST_SELENIUM_PORT);
}
if (defined('TEST_SELENIUM_BROWSER')) {
$this->setBrowser(TEST_SELENIUM_BROWSER);
}
$this->setBrowserUrl(TEST_SELENIUM_URL);
}
//
// OctoberCMS Helpers
//
protected function signInToBackend()
{
$this->open('backend');
$this->type("name=login", TEST_SELENIUM_USER);
$this->type("name=password", TEST_SELENIUM_PASS);
$this->click("//button[@type='submit']");
$this->waitForPageToLoad("30000");
}
/**
* Similar to the native getConfirmation() function
*/
protected function getSweetConfirmation($expectedText = null, $clickOk = true)
{
$this->waitForElementPresent("xpath=(//div[@class='sweet-alert showSweetAlert visible'])[1]");
if ($expectedText) {
$this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//h4", $expectedText);
}
$this->verifyText("//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary']", "OK");
if ($clickOk) {
$this->click("xpath=(//div[@class='sweet-alert showSweetAlert visible']//button[@class='confirm btn btn-primary'])[1]");
}
}
//
// Selenium helpers
//
protected function waitForElementPresent($target, $timeout = 60)
{
$second = 0;
while (true) {
if ($second >= $timeout) {
$this->fail('timeout');
}
try {
if ($this->isElementPresent($target)) {
break;
}
}
catch (Exception $e) {
}
sleep(1);
++$second;
}
}
protected function waitForElementNotPresent($target, $timeout = 60)
{
$second = 0;
while (true) {
if ($second >= $timeout) {
$this->fail('timeout');
}
try {
if (!$this->isElementPresent($target)) {
break;
}
}
catch (Exception $e) {
}
sleep(1);
++$second;
}
}
}

View File

@ -11,5 +11,5 @@ $loader = new October\Rain\Support\ClassLoader(
$loader->register();
$loader->addDirectories([
'modules',
'plugins'
'plugins',
]);

View File

@ -1,6 +1,6 @@
<?php
namespace October\Tests\Fixtures\Backend\Models;
namespace October\Core\Tests\Fixtures\Backend\Models;
use Backend\Models\User;

View File

@ -1,90 +0,0 @@
<?php
class AuthTest extends UiTestCase
{
public function testSignInAndOut()
{
$this->open('backend');
$cssLogoutLink = '#layout-mainmenu .mainmenu-accountmenu > ul > li:first-child > a';
try {
$this->assertTitle('Administration Area');
$this->assertTrue($this->isElementPresent("name=login"));
$this->assertTrue($this->isElementPresent("name=password"));
$this->assertTrue($this->isElementPresent("//button[@type='submit']"));
$this->verifyText("//button[@type='submit']", "Login");
}
catch (PHPUnit_Framework_AssertionFailedError $e) {
array_push($this->verificationErrors, $e->toString());
}
/*
* Sign in
*/
$this->type("name=login", TEST_SELENIUM_USER);
$this->type("name=password", TEST_SELENIUM_PASS);
$this->click("//button[@type='submit']");
$this->waitForPageToLoad("30000");
try {
$this->assertTitle('Dashboard | October CMS');
$this->assertTrue($this->isElementPresent('css='.$cssLogoutLink));
}
catch (PHPUnit_Framework_AssertionFailedError $e) {
array_push($this->verificationErrors, $e->toString());
}
$this->verifyText('css='.$cssLogoutLink, "Sign out");
/*
* Log out
*/
$this->click('css='.$cssLogoutLink);
$this->waitForPageToLoad("30000");
try {
$this->assertTitle('Administration Area');
}
catch (PHPUnit_Framework_AssertionFailedError $e) {
array_push($this->verificationErrors, $e->toString());
}
}
public function testPasswordReset()
{
$this->open('backend');
try {
$this->assertTrue($this->isElementPresent("link=exact:Forgot your password?"));
}
catch (PHPUnit_Framework_AssertionFailedError $e) {
array_push($this->verificationErrors, $e->toString());
}
$this->click('link=exact:Forgot your password?');
$this->waitForPageToLoad("30000");
try {
$this->assertTrue($this->isElementPresent("//button[@type='submit']"));
$this->verifyText("//button[@type='submit']", "Restore");
$this->assertTrue($this->isElementPresent("link=Cancel"));
}
catch (PHPUnit_Framework_AssertionFailedError $e) {
array_push($this->verificationErrors, $e->toString());
}
$this->type("name=login", TEST_SELENIUM_USER);
sleep(1);
$this->click("//button[@type='submit']");
$this->waitForPageToLoad("30000");
try {
$this->assertTitle('Administration Area');
$this->assertTrue($this->isElementPresent("css=p.flash-message.success"));
$this->verifyText("css=p.flash-message.success", "An email has been sent to your email address with password restore instructions.×");
}
catch (PHPUnit_Framework_AssertionFailedError $e) {
array_push($this->verificationErrors, $e->toString());
}
}
}

View File

@ -1,143 +0,0 @@
<?php
class TemplateTest extends UiTestCase
{
public function testOpenTemplates()
{
$this->signInToBackend();
$this->open('cms');
$this->waitForPageToLoad("30000");
// Fix the sidebar
$this->click("xpath=(//a[@class='fix-button'])[1]");
/*
* Page
*/
// Create a new page
$this->click("xpath=(//form[@data-template-type='page']//button[@data-control='create-template'])[1]");
$this->waitForElementPresent("name=settings[title]");
// Populate page details
$this->type('name=settings[title]', 'Functional Test Page');
$this->type('name=settings[url]', '/xxx/functional/test/page');
$this->type('name=fileName', 'xxx_functional_test_page');
// Save the new page
$this->click("xpath=(//a[@data-request='onSave'])[1]");
$this->waitForElementPresent("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm'])[1]");
// Close the tab
$this->click("xpath=(//li[@data-tab-id='page-".TEST_SELENIUM_THEME."-xxx_functional_test_page.htm']/span[@class='tab-close'])[1]");
// Reopen the tab
$this->waitForElementPresent("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]");
$this->click("xpath=(//div[@id='TemplateList-pageList-template-list']//li[@data-item-path='xxx_functional_test_page.htm']/a)[1]");
$this->waitForElementPresent("name=settings[title]");
sleep(1);
// Delete the page
$this->click("xpath=(//button[@data-request='onDelete'])[1]");
$this->getSweetConfirmation('Do you really want delete this page?');
// $this->assertTrue((bool)preg_match('/^Do you really want delete this page[\s\S]$/',$this->getConfirmation()));
$this->waitForElementNotPresent("name=settings[title]");
/*
* Partial
*/
// Click partials menu item
$this->click("xpath=(//li[@data-menu-item='partials']/a)[1]");
// Create a new partial
$this->click("xpath=(//form[@data-template-type='partial']//button[@data-control='create-template'])[1]");
$this->waitForElementPresent("name=fileName");
// Populate partial details
$this->type('name=fileName', 'xxx_functional_test_partial');
$this->type('name=settings[description]', 'Test partial');
// Save the new partial
$this->click("xpath=(//a[@data-request='onSave'])[1]");
$this->waitForElementPresent("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm'])[1]");
// Close the tab
$this->click("xpath=(//li[@data-tab-id='partial-".TEST_SELENIUM_THEME."-xxx_functional_test_partial.htm']/span[@class='tab-close'])[1]");
// Reopen the tab
$this->waitForElementPresent("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]");
$this->click("xpath=(//div[@id='TemplateList-partialList-template-list']//li[@data-item-path='xxx_functional_test_partial.htm']/a)[1]");
$this->waitForElementPresent("name=fileName");
sleep(1);
// Delete the partial
$this->click("xpath=(//button[@data-request='onDelete'])[1]");
$this->getSweetConfirmation('Do you really want delete this partial?');
$this->waitForElementNotPresent("name=fileName");
/*
* Layout
*/
// Click layouts menu item
$this->click("xpath=(//li[@data-menu-item='layouts']/a)[1]");
// Create a new layout
$this->click("xpath=(//form[@data-template-type='layout']//button[@data-control='create-template'])[1]");
$this->waitForElementPresent("name=fileName");
// Populate layout details
$this->type('name=fileName', 'xxx_functional_test_layout');
$this->type('name=settings[description]', 'Test layout');
// Save the new layout
$this->click("xpath=(//a[@data-request='onSave'])[1]");
$this->waitForElementPresent("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm'])[1]");
// Close the tab
$this->click("xpath=(//li[@data-tab-id='layout-".TEST_SELENIUM_THEME."-xxx_functional_test_layout.htm']/span[@class='tab-close'])[1]");
// Reopen the tab
$this->waitForElementPresent("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]");
$this->click("xpath=(//div[@id='TemplateList-layoutList-template-list']//li[@data-item-path='xxx_functional_test_layout.htm']/a)[1]");
$this->waitForElementPresent("name=fileName");
sleep(1);
// Delete the layout
$this->click("xpath=(//button[@data-request='onDelete'])[1]");
$this->getSweetConfirmation('Do you really want delete this layout?');
$this->waitForElementNotPresent("name=fileName");
/*
* Content
*/
// Click contents menu item
$this->click("xpath=(//li[@data-menu-item='content']/a)[1]");
// Create a new content
$this->click("xpath=(//form[@data-template-type='content']//button[@data-control='create-template'])[1]");
$this->waitForElementPresent("name=fileName");
// Populate content details
$this->type('name=fileName', 'xxx_functional_test_content.txt');
// Save the new content
$this->click("xpath=(//a[@data-request='onSave'])[1]");
$this->waitForElementPresent("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt'])[1]");
// Close the tab
$this->click("xpath=(//li[@data-tab-id='content-".TEST_SELENIUM_THEME."-xxx_functional_test_content.txt']/span[@class='tab-close'])[1]");
// Reopen the tab
$this->waitForElementPresent("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]");
$this->click("xpath=(//div[@id='TemplateList-contentList-template-list']//li[@data-item-path='xxx_functional_test_content.txt']/a)[1]");
$this->waitForElementPresent("name=fileName");
sleep(1);
// Delete the content
$this->click("xpath=(//button[@data-request='onDelete'])[1]");
$this->getSweetConfirmation('Do you really want delete this content file?');
$this->waitForElementNotPresent("name=fileName");
}
}

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="../../bootstrap/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="true"
syntaxCheck="false"
>
<testsuites>
<testsuite name="October Test Suite">
<directory>./</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -2,7 +2,7 @@
use Backend\Classes\AuthManager;
use October\Rain\Exception\SystemException;
class AuthManagerTest extends TestCase
class AuthManagerTest extends \October\Core\Tests\TestCase
{
public function setUp(): void
{

View File

@ -3,7 +3,7 @@
use Backend\Classes\Controller;
use Backend\Classes\NavigationManager;
class NavigationManagerTest extends TestCase
class NavigationManagerTest extends \October\Core\Tests\TestCase
{
public function testRegisterMenuItems()
{

View File

@ -3,7 +3,7 @@
use Backend\Classes\Controller;
use Backend\Classes\WidgetManager;
class WidgetManagerTest extends TestCase
class WidgetManagerTest extends \October\Core\Tests\TestCase
{
public function testListFormWidgets()
{

View File

@ -3,7 +3,7 @@
use Backend\Helpers\Backend;
use Backend\Helpers\Exception\DecompileException;
class BackendHelperTest extends TestCase
class BackendHelperTest extends \October\Core\Tests\TestCase
{
public function testDecompileAssets()
{

View File

@ -25,7 +25,7 @@ class ExampleExportModel extends ExportModel
}
}
class ExportModelTest extends TestCase
class ExportModelTest extends \October\Core\Tests\TestCase
{
//

View File

@ -16,7 +16,7 @@ class ExampleImportModel extends ImportModel
}
}
class ImportModelTest extends TestCase
class ImportModelTest extends \October\Core\Tests\TestCase
{
//

View File

@ -12,7 +12,7 @@ class ExampleTraitClass
}
}
class WidgetMakerTest extends TestCase
class WidgetMakerTest extends \October\Core\Tests\TestCase
{
/**
* The object under test.

View File

@ -2,10 +2,17 @@
use Backend\Widgets\Filter;
use Backend\Models\User;
use October\Tests\Fixtures\Backend\Models\UserFixture;
use October\Core\Tests\Fixtures\Backend\Models\UserFixture;
class FilterTest extends PluginTestCase
class FilterTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{
parent::setUp();
include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php';
}
public function testRestrictedScopeWithUserWithNoPermissions()
{
$user = new UserFixture;

View File

@ -2,15 +2,23 @@
use Backend\Widgets\Form;
use Illuminate\Database\Eloquent\Model;
use October\Tests\Fixtures\Backend\Models\UserFixture;
use October\Core\Tests\Fixtures\Backend\Models\UserFixture;
class FormTestModel extends Model
{
}
class FormTest extends PluginTestCase
class FormTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{
parent::setUp();
include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php';
}
public function testRestrictedFieldWithUserWithNoPermissions()
{
$user = new UserFixture;

View File

@ -3,10 +3,17 @@
use Backend\Models\User;
use Backend\Widgets\Lists;
use October\Rain\Exception\ApplicationException;
use October\Tests\Fixtures\Backend\Models\UserFixture;
use October\Core\Tests\Fixtures\Backend\Models\UserFixture;
class ListsTest extends PluginTestCase
class ListsTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{
parent::setUp();
include_once base_path() . '/tests/fixtures/backend/models/UserFixture.php';
}
public function testRestrictedColumnWithUserWithNoPermissions()
{
$user = new UserFixture;

View File

@ -28,7 +28,7 @@ class TestTemporaryCmsCompoundObject extends CmsCompoundObject
}
}
class CmsCompoundObjectTest extends TestCase
class CmsCompoundObjectTest extends \October\Core\Tests\TestCase
{
public function setUp() : void
{

View File

@ -9,7 +9,7 @@ use Cms\Classes\CmsException;
use Cms\Classes\CodeParser;
use October\Rain\Exception\SystemException;
class CmsExceptionTest extends TestCase
class CmsExceptionTest extends \October\Core\Tests\TestCase
{
//
// Tests

View File

@ -5,7 +5,7 @@ use Cms\Classes\Theme;
use Cms\Classes\Layout;
use October\Rain\Halcyon\Model;
class CmsObjectQueryTest extends TestCase
class CmsObjectQueryTest extends \October\Core\Tests\TestCase
{
public function setUp() : void
{

View File

@ -13,7 +13,7 @@ class TestTemporaryCmsObject extends CmsObject
protected $dirName = 'temporary';
}
class CmsObjectTest extends TestCase
class CmsObjectTest extends \October\Core\Tests\TestCase
{
public function testLoad()
{

View File

@ -8,7 +8,7 @@ use Cms\Classes\Layout;
use Cms\Classes\CodeParser;
use Cms\Classes\Controller;
class CodeParserTest extends TestCase
class CodeParserTest extends \October\Core\Tests\TestCase
{
public function setUp() : void
{

View File

@ -7,7 +7,7 @@ use Cms\Classes\Controller;
use Cms\Classes\CodeParser;
use Cms\Classes\ComponentManager;
class ComponentManagerTest extends TestCase
class ComponentManagerTest extends \October\Core\Tests\TestCase
{
public function setUp() : void
{

View File

@ -3,7 +3,7 @@
use Cms\Classes\Theme;
use Cms\Classes\Content;
class ContentTest extends TestCase
class ContentTest extends \October\Core\Tests\TestCase
{
public function testMarkdownContent()

View File

@ -4,7 +4,7 @@ use Cms\Classes\Theme;
use Cms\Classes\Controller;
use October\Rain\Halcyon\Model;
class ControllerTest extends TestCase
class ControllerTest extends \October\Core\Tests\TestCase
{
public function setUp() : void
{

View File

@ -2,7 +2,7 @@
use Cms\Classes\PartialStack;
class PartialStackTest extends TestCase
class PartialStackTest extends \October\Core\Tests\TestCase
{
public function testStackPartials()

View File

@ -3,7 +3,7 @@
use Cms\Classes\Router;
use Cms\Classes\Theme;
class RouterTest extends TestCase
class RouterTest extends \October\Core\Tests\TestCase
{
protected static $theme = null;

View File

@ -2,7 +2,7 @@
use Cms\Classes\Theme;
class ThemeTest extends TestCase
class ThemeTest extends \October\Core\Tests\TestCase
{
public function setUp() : void
{

View File

@ -2,7 +2,7 @@
use Cms\Helpers\File as FileHelper;
class FileTest extends TestCase
class FileTest extends \October\Core\Tests\TestCase
{
public function testValidateName()
{

View File

@ -13,7 +13,7 @@ class ExampleDbImportModel extends ImportModel
}
}
class ImportModelDbTest extends PluginTestCase
class ImportModelDbTest extends \October\Core\Tests\PluginTestCase
{
public function testGetImportFilePath()
{

View File

@ -3,7 +3,7 @@
use System\Models\File as FileModel;
use Database\Tester\Models\User;
class AttachManyModelTest extends PluginTestCase
class AttachManyModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -5,7 +5,7 @@ use Database\Tester\Models\User;
use Database\Tester\Models\SoftDeleteUser;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class AttachOneModelTest extends PluginTestCase
class AttachOneModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -3,7 +3,7 @@
use Database\Tester\Models\Role;
use Database\Tester\Models\Author;
class BelongsToManyModelTest extends PluginTestCase
class BelongsToManyModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -3,7 +3,7 @@
use Database\Tester\Models\Post;
use Database\Tester\Models\Author;
class BelongsToModelTest extends PluginTestCase
class BelongsToModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -4,7 +4,7 @@ use Database\Tester\Models\Post;
use Database\Tester\Models\Author;
use October\Rain\Database\Models\DeferredBinding;
class DeferredBindingTest extends PluginTestCase
class DeferredBindingTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -4,7 +4,7 @@ use Database\Tester\Models\Author;
use Database\Tester\Models\Post;
use October\Rain\Database\Collection;
class HasManyModelTest extends PluginTestCase
class HasManyModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -5,7 +5,7 @@ use Database\Tester\Models\Country;
use Database\Tester\Models\Post;
use October\Rain\Database\Collection;
class HasManyThroughModelTest extends PluginTestCase
class HasManyThroughModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -3,7 +3,7 @@
use Database\Tester\Models\Author;
use Database\Tester\Models\Phone;
class HasOneModelTest extends PluginTestCase
class HasOneModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -4,7 +4,7 @@ use Database\Tester\Models\Author;
use Database\Tester\Models\Phone;
use Database\Tester\Models\User;
class HasOneThroughModelTest extends PluginTestCase
class HasOneThroughModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -2,7 +2,7 @@
use Database\Tester\Models\Post;
class ModelTest extends PluginTestCase
class ModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -4,7 +4,7 @@ use Database\Tester\Models\Author;
use Database\Tester\Models\EventLog;
use October\Rain\Database\Collection;
class MorphManyModelTest extends PluginTestCase
class MorphManyModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -4,7 +4,7 @@ use Database\Tester\Models\Author;
use Database\Tester\Models\Post;
use Database\Tester\Models\Meta;
class MorphOneModelTest extends PluginTestCase
class MorphOneModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -4,7 +4,7 @@ use Database\Tester\Models\Post;
use Database\Tester\Models\Author;
use Database\Tester\Models\EventLog;
class MorphToModelTest extends PluginTestCase
class MorphToModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -3,7 +3,7 @@
use Carbon\Carbon;
use Database\Tester\Models\CategoryNested;
class NestedTreeModelTest extends PluginTestCase
class NestedTreeModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -2,7 +2,7 @@
use Database\Tester\Models\NullablePost;
class NullableModelTest extends PluginTestCase
class NullableModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -3,7 +3,7 @@
use Carbon\Carbon;
use Database\Tester\Models\RevisionablePost;
class RevisionableModelTest extends PluginTestCase
class RevisionableModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -3,7 +3,7 @@
use Carbon\Carbon;
use Database\Tester\Models\CategorySimple;
class SimpleTreeModelTest extends PluginTestCase
class SimpleTreeModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -2,7 +2,7 @@
use Database\Tester\Models\SluggablePost;
class SluggableModelTest extends PluginTestCase
class SluggableModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -8,7 +8,7 @@ use Database\Tester\Models\UserWithSoftAuthor;
use Database\Tester\Models\UserWithAuthorAndSoftDelete;
use Database\Tester\Models\UserWithSoftAuthorAndSoftDelete;
class SoftDeleteModelTest extends PluginTestCase
class SoftDeleteModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -2,7 +2,7 @@
use Database\Tester\Models\ValidationPost;
class ValidationModelTest extends PluginTestCase
class ValidationModelTest extends \October\Core\Tests\PluginTestCase
{
public function setUp() : void
{

View File

@ -14,7 +14,7 @@ class CmsThemeTemplateFixture extends Model
public $table = 'cms_theme_templates';
}
class AutoDatasourceTest extends PluginTestCase
class AutoDatasourceTest extends \October\Core\Tests\PluginTestCase
{
/**
* Array of model fixtures.

View File

@ -3,7 +3,7 @@
use Cms\Classes\Theme;
use System\Classes\CombineAssets;
class CombineAssetsTest extends TestCase
class CombineAssetsTest extends \October\Core\Tests\TestCase
{
public function setUp() : void
{

View File

@ -2,7 +2,7 @@
use System\Classes\PluginManager;
class CoreLangTest extends TestCase
class CoreLangTest extends \October\Core\Tests\TestCase
{
public function testValidationTranslator()
{

View File

@ -2,7 +2,7 @@
use System\Classes\MarkupManager;
class MarkupManagerTest extends TestCase
class MarkupManagerTest extends \October\Core\Tests\TestCase
{
public function setUp() : void

View File

@ -2,7 +2,7 @@
use System\Classes\MediaLibrary;
class MediaLibraryTest extends TestCase // @codingStandardsIgnoreLine
class MediaLibraryTest extends \October\Core\Tests\TestCase // @codingStandardsIgnoreLine
{
public function invalidPathsProvider()
{

View File

@ -1,7 +1,7 @@
<?php
use System\Classes\PluginManager;
class PluginManagerTest extends TestCase
class PluginManagerTest extends \October\Core\Tests\TestCase
{
public $manager;

View File

@ -2,7 +2,7 @@
use System\Controllers\Updates;
class UpdatesControllerTest extends TestCase
class UpdatesControllerTest extends \October\Core\Tests\TestCase
{
//

View File

@ -2,7 +2,7 @@
use System\Classes\VersionManager;
class VersionManagerTest extends TestCase
class VersionManagerTest extends \October\Core\Tests\TestCase
{
public function setUp() : void

View File

@ -6,7 +6,7 @@ class AssetMakerStub
use System\Traits\ViewMaker; // Needed for guessViewPath(), which is used to set default assetPath
}
class AssetMakerTest extends TestCase
class AssetMakerTest extends \October\Core\Tests\TestCase
{
private $stub;