├── .gitignore ├── package.json ├── composer.json ├── classes └── WPCC │ ├── Component │ ├── Generator │ │ ├── Actor.php │ │ ├── Sequence.php │ │ └── Sequence │ │ │ └── Faker.php │ ├── Factory │ │ ├── Callback │ │ │ └── AfterCreate.php │ │ ├── Attachment.php │ │ ├── User.php │ │ ├── Post.php │ │ ├── Blog.php │ │ ├── Network.php │ │ ├── Comment.php │ │ └── Term.php │ ├── Console │ │ └── Input │ │ │ └── ArgvInput.php │ └── Factory.php │ ├── Helper │ ├── PageObject │ │ └── LoginPage.php │ ├── PageObject.php │ └── Factory.php │ ├── SuiteManager.php │ ├── Subscriber │ ├── Bootstrap.php │ └── AutoRebuild.php │ ├── Util │ └── Debug.php │ ├── Configuration.php │ ├── Command │ ├── Build.php │ ├── Bootstrap.php │ └── Run.php │ ├── Module │ ├── BrowserStack.php │ ├── WebDriver.php │ ├── WordPress.php │ ├── Wpdb.php │ └── MailtrapIO.php │ ├── Codecept.php │ ├── CLI │ ├── Selenium.php │ └── Codeception.php │ └── TestLoader.php ├── wp-codeception.php ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /vendor/ 3 | /logs/ 4 | /npm-debug.log -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-codeception", 3 | "version": "1.0.2", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "" 8 | }, 9 | "author": "10up Inc", 10 | "license": "GPL-2.0", 11 | "devDependencies": { 12 | "phantomjs": "^1.9.16", 13 | "selenium-server": "^2.45.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "10up/wp-codeception", 3 | "description": "The WordPress Plugin which integrates with the Codeception PHP testing framework and allows you to write and run Codeception tests for WordPress via WP CLI.", 4 | "version": "1.0.3", 5 | "type": "wordpress-plugin", 6 | "keywords": [ 7 | "wordpress", 8 | "bdd", 9 | "acceptance testing", 10 | "functional testing", 11 | "unit testing", 12 | "tdd" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "10up Inc", 17 | "homepage": "http://10up.com/" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=5.4.0", 22 | "codeception/codeception": "2.0.11", 23 | "fzaninotto/faker": "1.5.*@dev", 24 | "composer/installers": "^1.0" 25 | }, 26 | "autoload": { 27 | "psr-0": { 28 | "WPCC": "classes" 29 | } 30 | }, 31 | "scripts": { 32 | "post-install-cmd": [ 33 | "npm install" 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Generator/Actor.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 45 | $this->modules = Configuration::modules( $settings ); 46 | $this->actions = Configuration::actions( $this->modules ); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /classes/WPCC/Helper/PageObject/LoginPage.php: -------------------------------------------------------------------------------- 1 | _actor; 49 | 50 | $I->amOnPage( wp_login_url() ); 51 | $I->fillField( self::USERNAME_FIELD, $username ); 52 | $I->fillField( self::PASSWORD_FIELD, $password ); 53 | $I->click( self::LOGIN_BUTTON ); 54 | $I->seeCookie( LOGGED_IN_COOKIE ); 55 | 56 | return $this; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /classes/WPCC/Helper/PageObject.php: -------------------------------------------------------------------------------- 1 | _actor = $I; 53 | } 54 | 55 | /** 56 | * Creates new instance of page object for an actor and returns it. 57 | * 58 | * @since 1.0.0 59 | * 60 | * @static 61 | * @access public 62 | * @param \Codeception\Actor $I The actor to create a page object for. 63 | * @return \WPCC\Helper\PageObject The new instance of a page object. 64 | */ 65 | public static function of( \Codeception\Actor $I ) { 66 | return new static( $I ); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/Callback/AfterCreate.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 53 | } 54 | 55 | /** 56 | * Calls the callback and returns results of the call. 57 | * 58 | * @since 1.0.0 59 | * 60 | * @access public 61 | * @param mixed $object The incoming object. 62 | * @return mixed The results of the callback call. 63 | */ 64 | public function call( $object ) { 65 | return call_user_func( $this->callback, $object ); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /classes/WPCC/SuiteManager.php: -------------------------------------------------------------------------------- 1 | settings['path'] ); 41 | if ( ! empty( $test ) && has_action( $test ) ) { 42 | $testLoader->loadTest( $test ); 43 | } else { 44 | $testLoader->loadTests(); 45 | } 46 | 47 | $tests = $testLoader->getTests(); 48 | foreach ( $tests as $test ) { 49 | $this->addToSuite( $test ); 50 | } 51 | } 52 | 53 | /** 54 | * Initializes modules. 55 | * 56 | * @since 1.0.0 57 | * 58 | * @access protected 59 | */ 60 | protected function initializeModules() { 61 | self::$modules = Configuration::modules( $this->settings ); 62 | self::$actions = Configuration::actions( self::$modules ); 63 | 64 | foreach ( self::$modules as $module ) { 65 | $module->_initialize(); 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /classes/WPCC/Subscriber/Bootstrap.php: -------------------------------------------------------------------------------- 1 | getSettings(); 46 | if ( ! isset( $settings['bootstrap'] ) || ! filter_var( $settings['bootstrap'], FILTER_VALIDATE_BOOLEAN ) ) { 47 | return; 48 | } 49 | 50 | $bootstrap = $settings['path'] . $settings['bootstrap']; 51 | if ( ! is_file( $bootstrap ) ) { 52 | throw new ConfigurationException( "Bootstrap file {$bootstrap} can't be loaded" ); 53 | } 54 | 55 | require_once $bootstrap; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Generator/Sequence.php: -------------------------------------------------------------------------------- 1 | _index = $start; 64 | $this->_template = $template; 65 | } 66 | 67 | /** 68 | * Generates next string and shifts sequence index. 69 | * 70 | * @since 1.0.0 71 | * 72 | * @access public 73 | * @return string A string based on the sequence template. 74 | */ 75 | public function next() { 76 | return sprintf( $this->_template, $this->_index++ ); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Generator/Sequence/Faker.php: -------------------------------------------------------------------------------- 1 | _faker = \Faker\Factory::create(); 57 | } 58 | 59 | /** 60 | * Generates next string and shifts sequence index. 61 | * 62 | * @since 1.0.0 63 | * 64 | * @access public 65 | * @return string A string based on the sequence template. 66 | */ 67 | public function next() { 68 | $this->_index++; 69 | return $this->_faker->{$this->_template}; 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /classes/WPCC/Util/Debug.php: -------------------------------------------------------------------------------- 1 | 37 | * 46 | * 47 | * 48 | * @since 1.0.2 49 | * @uses sprintf() to build debug message. 50 | * @uses \Codeception\Util\Debug::debug() to print data to screen. 51 | * 52 | * @static 53 | * @access public 54 | * @param string $message The message pattern. 55 | * @param type $args 56 | */ 57 | public static function debugf( $message, $args ) { 58 | $args = array_slice( func_get_args(), 1 ); 59 | if ( count( $args ) == 1 && is_array( $args[0] ) ) { 60 | $args = $args[0]; 61 | } 62 | 63 | array_unshift( $args, $message ); 64 | $message = call_user_func_array( 'sprintf', $args ); 65 | self::debug( $message ); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /classes/WPCC/Subscriber/AutoRebuild.php: -------------------------------------------------------------------------------- 1 | getSettings(); 46 | $guyFile = $settings['path'] . $settings['class_name'] . '.php'; 47 | 48 | // load guy class to see hash 49 | $handle = fopen( $guyFile, "r" ); 50 | if ( $handle ) { 51 | $line = fgets( $handle ); 52 | if ( preg_match( '~\[STAMP\] ([a-f0-9]*)~', $line, $matches ) ) { 53 | $hash = $matches[1]; 54 | $currentHash = Actor::genHash( SuiteManager::$actions, $settings ); 55 | 56 | // regenerate guy class when hashes do not match 57 | if ( $hash != $currentHash ) { 58 | codecept_debug( "Rebuilding {$settings['class_name']}..." ); 59 | $guyGenerator = new Actor( $settings ); 60 | fclose( $handle ); 61 | $generated = $guyGenerator->produce(); 62 | file_put_contents( $guyFile, $generated ); 63 | return; 64 | } 65 | } 66 | 67 | fclose( $handle ); 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Console/Input/ArgvInput.php: -------------------------------------------------------------------------------- 1 | getGlobalConfig( $configFile ); 46 | $suites = $this->getSuites( $configFile ); 47 | 48 | $path = pathinfo( $configFile ); 49 | $dir = isset( $path['dirname'] ) ? $path['dirname'] : getcwd(); 50 | 51 | foreach ( $config['include'] as $subConfig ) { 52 | $this->output->writeln( "Included Configuration: $subConfig" ); 53 | $this->buildActorsForConfig( $dir . DIRECTORY_SEPARATOR . $subConfig ); 54 | } 55 | 56 | if ( !empty( $suites ) ) { 57 | $this->output->writeln( "Building Actor classes for suites: " . implode( ', ', $suites ) . '' ); 58 | } 59 | foreach ( $suites as $suite ) { 60 | $settings = $this->getSuiteConfig( $suite, $configFile ); 61 | $gen = new ActorGenerator( $settings ); 62 | $this->output->writeln( '' . Configuration::config()['namespace'] . '\\' . $gen->getActorName() . " includes modules: " . implode( ', ', $gen->getModules() ) ); 63 | $contents = $gen->produce(); 64 | 65 | @mkdir( $settings['path'], 0755, true ); 66 | $file = $settings['path'] . $this->getClassName( $settings['class_name'] ) . '.php'; 67 | $this->save( $file, $contents, true ); 68 | $this->output->writeln( "{$settings['class_name']}.php generated successfully. " . $gen->getNumMethods() . " methods added" ); 69 | } 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/Attachment.php: -------------------------------------------------------------------------------- 1 | _debug( 'Reading mime type of the file: ' . $args['file'] ); 46 | 47 | $filetype = wp_check_filetype( basename( $args['file'] ), null ); 48 | if ( ! empty( $filetype['type'] ) ) { 49 | $args['post_mime_type'] = $filetype['type']; 50 | $this->_debug( 'Mime type found: ' . $filetype['type'] ); 51 | } else { 52 | $this->_debug( 'Mime type not found' ); 53 | } 54 | } 55 | } 56 | 57 | $attachment_id = wp_insert_attachment( $args ); 58 | if ( $attachment_id ) { 59 | $this->_debug( 60 | 'Generated attachment ID: %d (file: %s)', 61 | $attachment_id, 62 | ! empty( $args['file'] ) ? $args['file'] : 'not-provided' 63 | ); 64 | 65 | $this->_debug( 'Generating attachment metadata' ); 66 | $metadata = wp_generate_attachment_metadata( $attachment_id, $args['file'] ); 67 | if ( is_wp_error( $metadata ) ) { 68 | $this->_debug( 69 | 'Attachment metadata generation failed with error [%s] %s', 70 | $metadata->get_error_code(), 71 | $metadata->get_error_message() 72 | ); 73 | } elseif ( empty( $metadata ) ) { 74 | $this->_debug( 'Attachment metadata generation failed' ); 75 | } else { 76 | wp_update_attachment_metadata( $attachment_id, $metadata ); 77 | } 78 | } else { 79 | $this->_debug( 'Attachment generation failed' ); 80 | } 81 | 82 | return $attachment_id; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /classes/WPCC/Module/BrowserStack.php: -------------------------------------------------------------------------------- 1 | requiredFields[] = 'username'; 45 | $this->requiredFields[] = 'access_key'; 46 | 47 | parent::__construct( $config ); 48 | } 49 | 50 | /** 51 | * Initializes webdriver. 52 | * 53 | * @since 1.0.0 54 | * 55 | * @access public 56 | */ 57 | public function _initialize() { 58 | $this->wd_host = sprintf( 'http://%s:%s@hub.browserstack.com/wd/hub', $this->config['username'], $this->config['access_key'] ); 59 | 60 | $this->capabilities = $this->config['capabilities']; 61 | $this->capabilities[ \WebDriverCapabilityType::BROWSER_NAME ] = $this->config['browser']; 62 | if ( ! empty( $this->config['version'] ) ) { 63 | $this->capabilities[ \WebDriverCapabilityType::VERSION ] = $this->config['version']; 64 | } 65 | 66 | $this->webDriver = \RemoteWebDriver::create( $this->wd_host, $this->capabilities ); 67 | $this->webDriver->manage()->timeouts()->implicitlyWait( $this->config['wait'] ); 68 | 69 | $this->initialWindowSize(); 70 | } 71 | 72 | /** 73 | * Setup initial window size. 74 | * 75 | * @since 1.0.0 76 | * 77 | * @access protected 78 | */ 79 | protected function initialWindowSize() { 80 | if ( isset( $this->config['resolution'] ) ) { 81 | if ( $this->config['resolution'] == 'maximize' ) { 82 | $this->maximizeWindow(); 83 | } else { 84 | $size = explode( 'x', $this->config['resolution'] ); 85 | if ( count( $size ) == 2 ) { 86 | $this->resizeWindow( intval( $size[0] ), intval( $size[1] ) ); 87 | } 88 | } 89 | } 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /classes/WPCC/Codecept.php: -------------------------------------------------------------------------------- 1 | dispatcher->addSubscriber( new \Codeception\Subscriber\ErrorHandler() ); 41 | $this->dispatcher->addSubscriber( new \WPCC\Subscriber\Bootstrap() ); 42 | $this->dispatcher->addSubscriber( new \Codeception\Subscriber\Module() ); 43 | $this->dispatcher->addSubscriber( new \Codeception\Subscriber\BeforeAfterTest() ); 44 | $this->dispatcher->addSubscriber( new \WPCC\Subscriber\AutoRebuild() ); 45 | 46 | // optional 47 | if ( !$this->options['silent'] ) { 48 | $this->dispatcher->addSubscriber( new \Codeception\Subscriber\Console( $this->options ) ); 49 | } 50 | 51 | if ( $this->options['fail-fast'] ) { 52 | $this->dispatcher->addSubscriber( new \Codeception\Subscriber\FailFast() ); 53 | } 54 | 55 | if ( $this->options['coverage'] ) { 56 | $this->dispatcher->addSubscriber( new \Codeception\Coverage\Subscriber\Local( $this->options ) ); 57 | $this->dispatcher->addSubscriber( new \Codeception\Coverage\Subscriber\LocalServer( $this->options ) ); 58 | $this->dispatcher->addSubscriber( new \Codeception\Coverage\Subscriber\RemoteServer( $this->options ) ); 59 | $this->dispatcher->addSubscriber( new \Codeception\Coverage\Subscriber\Printer( $this->options ) ); 60 | } 61 | 62 | // extensions 63 | foreach ( $this->extensions as $subscriber ) { 64 | $this->dispatcher->addSubscriber( $subscriber ); 65 | } 66 | } 67 | 68 | /** 69 | * Runs suite tests. 70 | * 71 | * @since 1.0.0 72 | * 73 | * @access public 74 | * @param array $settings The array of suite settings. 75 | * @param string $suite The suite name to run. 76 | * @param string $test The test name to run. 77 | * @return \PHPUnit_Framework_TestResult The suite execution results. 78 | */ 79 | public function runSuite( $settings, $suite, $test = null ) { 80 | $suiteManager = new SuiteManager( $this->dispatcher, $suite, $settings ); 81 | $suiteManager->initialize(); 82 | $suiteManager->loadTests( $test ); 83 | $suiteManager->run( $this->runner, $this->result, $this->options ); 84 | 85 | return $this->result; 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /classes/WPCC/Module/WebDriver.php: -------------------------------------------------------------------------------- 1 | requiredFields ); 43 | if ( ! empty( $url_index ) ) { 44 | unset( $this->requiredFields[ $url_index ] ); 45 | } 46 | 47 | // add home url to the config 48 | $this->config['url'] = home_url(); 49 | 50 | // call parent constructor 51 | parent::__construct( $config ); 52 | 53 | // add pahntomjs path if needed 54 | if ( 'phantomjs' == $this->config['browser'] ) { 55 | $phantomjs_binary = WPCC_ABSPATH . '/node_modules/phantomjs/bin/phantomjs'; 56 | if ( ! isset( $this->config['capabilities'] ) ) { 57 | $this->config['capabilities'] = array( 58 | 'phantomjs.binary.path' => $phantomjs_binary, 59 | ); 60 | } elseif ( empty( $this->config['capabilities']['phantomjs.binary.path'] ) ) { 61 | $this->config['capabilities']['phantomjs.binary.path'] = $phantomjs_binary; 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Goes to a specific admin page. Uses amOnPage method to do a redirect. 68 | * 69 | * @since 1.0.0 70 | * 71 | * @access public 72 | * @param string $path Optional path relative to the admin url. 73 | */ 74 | public function amOnAdminPage( $path = '' ) { 75 | $this->amOnPage( admin_url( $path ) ); 76 | } 77 | 78 | /** 79 | * Clicks admin menu item. Since we are using a WebDriver the first click on 80 | * a parent element will lead to opening a submenu group. So if we need to 81 | * click on submenu item, we need to do it in two steps. 82 | * 83 | * @since 1.0.0 84 | * 85 | * @access public 86 | * @param string $menu The menu item to click on. 87 | * @param string $parent The parent menu item to click on first. 88 | */ 89 | public function clickAdminMenu( $menu, $parent = null ) { 90 | if ( $parent ) { 91 | $this->click( $parent, '#adminmenu' ); 92 | } 93 | 94 | $this->click( $menu, '#adminmenu' ); 95 | } 96 | 97 | /** 98 | * Fills TinyMCE editor. 99 | * 100 | *

101 | 	 * fillTinyMCEField( 'content', 'Lorem ipsum dolor sit...' );
103 | 	 * ?>
104 | 	 * 
105 | * 106 | * @since 1.0.0 107 | * 108 | * @access public 109 | * @param string $editor_id The TinyMCE's editor id. 110 | * @param string $value The value to insert into the editor. 111 | */ 112 | public function fillTinyMCEField( $editor_id, $value ) { 113 | $script = sprintf( "tinyMCE.get('%s').setContent('%s')", esc_js( $editor_id ), esc_js( $value ) ); 114 | $this->webDriver->executeScript( $script ); 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /classes/WPCC/CLI/Selenium.php: -------------------------------------------------------------------------------- 1 | _get_executable(); 66 | $pids = trim( shell_exec( "pgrep -l -f {$selenium}" ) ); 67 | $pids = explode( PHP_EOL, (string) $pids ); 68 | 69 | if ( ! empty( $pids ) && count( $pids ) >= 1 ) { 70 | foreach ( $pids as $pid ) { 71 | shell_exec( "kill -15 {$pid} > /dev/null 2>/dev/null" ); 72 | } 73 | \WP_CLI::success( 'Selenium server is stopped.' ); 74 | } else { 75 | \WP_CLI::warning( 'Selenium server is not started yet.' ); 76 | } 77 | } 78 | 79 | /** 80 | * Starts selenium server. 81 | * 82 | * ### OPTIONS 83 | * 84 | * ### EXAMPLE 85 | * 86 | * wp selenium start 87 | * 88 | * @since 1.0.0 89 | * @alias run 90 | * 91 | * @access public 92 | * @param array $args Unassociated array of arguments passed to this command. 93 | * @param array $assoc_args Associated array of arguments passed to this command. 94 | */ 95 | public function start( $args, $assoc_args ) { 96 | $selenium = $this->_get_executable(); 97 | if ( is_executable( $selenium ) ) { 98 | 99 | $pids = explode( PHP_EOL, trim( shell_exec( "pgrep -f {$selenium}" ) ) ); 100 | if ( count( $pids ) < 2 ) { 101 | shell_exec( "{$selenium} > /dev/null 2>/dev/null &" ); 102 | \WP_CLI::success( 'Selenium server started.' ); 103 | } else { 104 | \WP_CLI::warning( 'Selenium server is already started.' ); 105 | } 106 | 107 | } else { 108 | \WP_CLI::error( 'Selenium server is not executable or not installed.' ); 109 | } 110 | } 111 | 112 | /** 113 | * Restarts selenium server. 114 | * 115 | * ### OPTIONS 116 | * 117 | * ### EXAMPLE 118 | * 119 | * wp selenium restart 120 | * 121 | * @since 1.0.0 122 | * 123 | * @access public 124 | * @param array $args Unassociated array of arguments passed to this command. 125 | * @param array $assoc_args Associated array of arguments passed to this command. 126 | */ 127 | public function restart( $args, $assoc_args ) { 128 | $this->stop( $args, $assoc_args ); 129 | $this->start( $args, $assoc_args ); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /classes/WPCC/Command/Bootstrap.php: -------------------------------------------------------------------------------- 1 | $actor . $this->actorSuffix, 46 | 'modules' => array( 47 | 'enabled' => array( 48 | 'WebDriver', 49 | 'WordPress', 50 | "{$actor}Helper", 51 | ), 52 | 'config' => array( 53 | 'WebDriver' => array( 54 | 'browser' => 'phantomjs', 55 | ), 56 | ), 57 | ), 58 | ); 59 | 60 | $str = "# Codeception Test Suite Configuration\n\n"; 61 | $str .= "# suite for acceptance tests.\n"; 62 | $str .= "# perform tests in browser using the WebDriver or PhpBrowser.\n"; 63 | $str .= "# If you need both WebDriver and PHPBrowser tests - create a separate suite.\n\n"; 64 | 65 | $str .= Yaml::dump( $suiteConfig, 5 ); 66 | $this->createSuite( 'acceptance', $actor, $str ); 67 | } 68 | 69 | /** 70 | * Creates functional suite. 71 | * 72 | * @since 1.0.0 73 | * 74 | * @access protected 75 | * @param string $actor The actor name. 76 | */ 77 | protected function createFunctionalSuite( $actor = 'Functional' ) { 78 | $suiteConfig = array( 79 | 'class_name' => $actor . $this->actorSuffix, 80 | 'modules' => array( 81 | 'enabled' => array( 'WordPress', $actor . 'Helper' ) 82 | ), 83 | ); 84 | 85 | $str = "# Codeception Test Suite Configuration\n\n"; 86 | $str .= "# suite for functional (integration) tests.\n"; 87 | $str .= "# emulate web requests and make application process them.\n\n"; 88 | $str .= Yaml::dump( $suiteConfig, 2 ); 89 | 90 | $this->createSuite( 'functional', $actor, $str ); 91 | } 92 | 93 | /** 94 | * Creates global config file. 95 | * 96 | * @since 1.0.0 97 | * 98 | * @access public 99 | */ 100 | public function createGlobalConfig() { 101 | $basicConfig = array( 102 | 'actor' => $this->actorSuffix, 103 | 'paths' => array( 104 | 'tests' => 'tests', 105 | 'log' => $this->logDir, 106 | 'data' => $this->dataDir, 107 | 'helpers' => $this->helperDir 108 | ), 109 | 'settings' => array( 110 | 'bootstrap' => '_bootstrap.php', 111 | 'colors' => strtoupper( substr( PHP_OS, 0, 3 ) ) != 'WIN', 112 | 'memory_limit' => WP_MAX_MEMORY_LIMIT 113 | ), 114 | ); 115 | 116 | $str = Yaml::dump( $basicConfig, 4 ); 117 | if ( $this->namespace ) { 118 | $str = "namespace: {$this->namespace} \n" . $str; 119 | } 120 | 121 | file_put_contents( 'codeception.yml', $str ); 122 | } 123 | 124 | /** 125 | * Creates appropriate folders. 126 | * 127 | * @since 1.0.0 128 | * 129 | * @access protected 130 | */ 131 | protected function createDirs() { 132 | @mkdir( 'tests' ); 133 | @mkdir( $this->logDir ); 134 | @mkdir( $this->dataDir ); 135 | @mkdir( $this->helperDir ); 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /classes/WPCC/Module/WordPress.php: -------------------------------------------------------------------------------- 1 | assertNotEmpty( $metas, $message ); 48 | 49 | if ( func_num_args() > 2 ) { 50 | $message = sprintf( 'User meta %s does not contain expected value', $meta_key ); 51 | $this->assertContains( $meta_value, $metas, $message ); 52 | } 53 | } 54 | 55 | /** 56 | * Checks if user meta doesn't exists. 57 | * 58 | * @since 1.0.0 59 | * 60 | * @access public 61 | * @param int $user_id The user id. 62 | * @param string $meta_key The meta key to check. 63 | * @param mixed $meta_value The meta value to check 64 | */ 65 | public function dontSeeUserMetaFor( $user_id, $meta_key, $meta_value = null ) { 66 | $metas = get_user_meta( $user_id, $meta_key ); 67 | 68 | if ( func_num_args() > 2 ) { 69 | $message = sprintf( 'User meta %s contains not expected value', $meta_key ); 70 | $this->assertNotContains( $meta_value, $metas, $message ); 71 | } else { 72 | $message = sprintf( 'User meta %s is not empty', $meta_key ); 73 | $this->assertEmpty( $metas, $message ); 74 | } 75 | } 76 | 77 | /** 78 | * Checks a post meta exists for a post. 79 | * 80 | * @since 1.0.0 81 | * 82 | * @access public 83 | * @param int $post_id The post id. 84 | * @param string $meta_key The meta key to check. 85 | * @param mixed $meta_value The meta value to check 86 | */ 87 | public function seePostMetaFor( $post_id, $meta_key, $meta_value = null ) { 88 | $metas = get_post_meta( $post_id, $meta_key ); 89 | 90 | $message = sprintf( 'Post meta %s does not exist', $meta_key ); 91 | $this->assertNotEmpty( $metas, $message ); 92 | 93 | if ( func_num_args() > 2 ) { 94 | $message = sprintf( 'Post meta %s does not contain expected value', $meta_key ); 95 | $this->assertContains( $meta_value, $metas, $message ); 96 | } 97 | } 98 | 99 | /** 100 | * Checks if post meta doesn't exists. 101 | * 102 | * @since 1.0.0 103 | * 104 | * @access public 105 | * @param int $post The post id. 106 | * @param string $meta_key The meta key to check. 107 | * @param mixed $meta_value The meta value to check 108 | */ 109 | public function dontSeePostMetaFor( $post, $meta_key, $meta_value = null ) { 110 | $metas = get_post_meta( $post, $meta_key ); 111 | 112 | if ( func_num_args() > 2 ) { 113 | $message = sprintf( 'Post meta %s contains not expected value', $meta_key ); 114 | $this->assertNotContains( $meta_value, $metas, $message ); 115 | } else { 116 | $message = sprintf( 'Post meta %s is not empty', $meta_key ); 117 | $this->assertEmpty( $metas, $message ); 118 | } 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/User.php: -------------------------------------------------------------------------------- 1 | new FakerSequence( 'name' ), 45 | 'user_pass' => 'password', 46 | 'user_email' => new FakerSequence( 'email' ), 47 | ) ); 48 | } 49 | 50 | /** 51 | * Generates a new user. 52 | * 53 | * @since 1.0.0 54 | * 55 | * @access protected 56 | * @param array $args The array of arguments to use during a new user creation. 57 | * @return int|\WP_Error The newly created user's ID on success, otherwise a WP_Error object. 58 | */ 59 | protected function _createObject( $args ) { 60 | $user_id = wp_insert_user( $args ); 61 | if ( $user_id && ! is_wp_error( $user_id ) ) { 62 | $this->_debug( 'Generated user with ID: ' . $user_id ); 63 | } elseif ( is_wp_error( $user_id ) ) { 64 | $this->_debug( 65 | 'User generation failed with message [%s] %s', 66 | $user_id->get_error_code(), 67 | $user_id->get_error_message() 68 | ); 69 | } else { 70 | $this->_debug( 'User generation failed' ); 71 | } 72 | 73 | 74 | return $user_id; 75 | } 76 | 77 | /** 78 | * Updates generated user. 79 | * 80 | * @since 1.0.0 81 | * 82 | * @access protected 83 | * @param mixed $user_id The user id to update. 84 | * @param array $fields The array of fields to update. 85 | * @return mixed Updated user ID on success, otherwise a WP_Error object. 86 | */ 87 | protected function _updateObject( $user_id, $fields ) { 88 | $fields['ID'] = $user_id; 89 | $updated = wp_update_user( $fields ); 90 | if ( $updated && ! is_wp_error( $updated ) ) { 91 | $this->_debug( 'Updated user ' . $user_id ); 92 | } elseif ( is_wp_error( $updated ) ) { 93 | $this->_debug( 94 | 'Update failed for user %d with message [%s] %s', 95 | $user_id, 96 | $updated->get_error_code(), 97 | $updated->get_error_message() 98 | ); 99 | } else { 100 | $this->_debug( 'Update failed for user ' . $user_id ); 101 | } 102 | 103 | return $updated; 104 | } 105 | 106 | /** 107 | * Deletes previously generated user. 108 | * 109 | * @since 1.0.0 110 | * 111 | * @access protected 112 | * @param int $user_id The user id to delete. 113 | * @return boolean TRUEY on success, otherwise FALSE. 114 | */ 115 | protected function _deleteObject( $user_id ) { 116 | $user = get_user_by( 'id', $user_id ); 117 | if ( ! $user ) { 118 | return false; 119 | } 120 | 121 | $deleted = is_multisite() 122 | ? wpmu_delete_user( $user_id ) 123 | : wp_delete_user( $user_id ); 124 | 125 | if ( $deleted ) { 126 | $this->_debug( 'Deleted user with ID: ' . $user_id ); 127 | return true; 128 | } 129 | 130 | $this->_debug( 'User removal failed for ID: ' . $user_id ); 131 | return false; 132 | } 133 | 134 | /** 135 | * Returns generated user by id. 136 | * 137 | * @since 1.0.0 138 | * 139 | * @access public 140 | * @param int $user_id The user id. 141 | * @return \WP_User The generated user. 142 | */ 143 | public function getObjectById( $user_id ) { 144 | return new \WP_User( $user_id ); 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/Post.php: -------------------------------------------------------------------------------- 1 | 'publish', 45 | 'post_title' => new Sequence( 'Post title %s' ), 46 | 'post_content' => new Sequence( 'Post content %s' ), 47 | 'post_excerpt' => new Sequence( 'Post excerpt %s' ), 48 | 'post_type' => 'post' 49 | ) ); 50 | } 51 | 52 | /** 53 | * Generates a new post. 54 | * 55 | * @since 1.0.0 56 | * 57 | * @access protected 58 | * @param array $args The array of arguments to use during a new post creation. 59 | * @return int|\WP_Error The newly created post's ID on success, otherwise a WP_Error object. 60 | */ 61 | protected function _createObject( $args ) { 62 | $post_id = wp_insert_post( $args, true ); 63 | if ( $post_id && ! is_wp_error( $post_id ) ) { 64 | $this->_debug( 'Generated post ID: ' . $post_id ); 65 | } elseif ( is_wp_error( $post_id ) ) { 66 | $this->_debug( 67 | 'Post generation failed with message [%s] %s', 68 | $post_id->get_error_code(), 69 | $post_id->get_error_messages() 70 | ); 71 | } else { 72 | $this->_debug( 'Post generation failed' ); 73 | } 74 | 75 | return $post_id; 76 | } 77 | 78 | /** 79 | * Updates generated object. 80 | * 81 | * @since 1.0.0 82 | * 83 | * @access protected 84 | * @param mixed $post_id The post id to update. 85 | * @param array $fields The array of fields to update. 86 | * @return mixed Updated post ID on success, otherwise 0 or a WP_Error object. 87 | */ 88 | protected function _updateObject( $post_id, $fields ) { 89 | $fields['ID'] = $post_id; 90 | $updated = wp_update_post( $fields ); 91 | if ( $updated && ! is_wp_error( $updated ) ) { 92 | $this->_debug( 'Updated post ' . $post_id ); 93 | } elseif ( is_wp_error( $updated ) ) { 94 | $this->_debug( 95 | 'Update failed for post %d with message [%s] %s', 96 | $post_id, 97 | $updated->get_error_code(), 98 | $updated->get_error_message() 99 | ); 100 | } else { 101 | $this->_debug( 'Update failed for post ' . $post_id ); 102 | } 103 | 104 | return $updated; 105 | } 106 | 107 | /** 108 | * Deletes previously generated post object. 109 | * 110 | * @since 1.0.0 111 | * 112 | * @access protected 113 | * @param int $post_id The post id to delete. 114 | * @return boolean TRUE on success, otherwise FALSE. 115 | */ 116 | protected function _deleteObject( $post_id ) { 117 | $post = get_post( $post_id ); 118 | if ( ! $post ) { 119 | return false; 120 | } 121 | 122 | $deleted = wp_delete_post( $post_id, true ); 123 | if ( $deleted ) { 124 | $this->_debug( 'Deleted post with ID: ' . $post_id ); 125 | return true; 126 | } 127 | 128 | $this->_debug( 'Post removal failed for ID: ' . $post_id ); 129 | return false; 130 | } 131 | 132 | /** 133 | * Returns generated post by id. 134 | * 135 | * @since 1.0.0 136 | * 137 | * @access public 138 | * @param int $post_id The post id. 139 | * @return \WP_Post|null The generated post on success, otherwise NULL. 140 | */ 141 | public function getObjectById( $post_id ) { 142 | return get_post( $post_id ); 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /classes/WPCC/Helper/Factory.php: -------------------------------------------------------------------------------- 1 | _factories = array( 63 | 'user' => new \WPCC\Component\Factory\User(), 64 | 'post' => new \WPCC\Component\Factory\Post(), 65 | 'attachment' => new \WPCC\Component\Factory\Attachment(), 66 | 'comment' => new \WPCC\Component\Factory\Comment(), 67 | 'term' => new TermsFactory(), 68 | 'category' => new TermsFactory( 'category' ), 69 | 'tag' => new TermsFactory( 'post_tag' ), 70 | ); 71 | 72 | if ( is_multisite() ) { 73 | $this->_factories['blog'] = new \WPCC\Component\Factory\Blog(); 74 | $this->_factories['network'] = new \WPCC\Component\Factory\Network(); 75 | } 76 | } 77 | 78 | /** 79 | * Creates a new instance of the factory and returns it. 80 | * 81 | * @since 1.0.0 82 | * 83 | * @static 84 | * @access public 85 | * @return \WPCC\Helper\Factory The factory instance. 86 | */ 87 | public static function create() { 88 | return new static(); 89 | } 90 | 91 | /** 92 | * Returns concrete factory. 93 | * 94 | * @since 1.0.0 95 | * 96 | * @access public 97 | * @param string $factory The factory name. 98 | * @return \WPCC\Component\Factory The factory object if available, otherwise NULL. 99 | */ 100 | public function __get( $factory ) { 101 | return isset( $this->_factories[ $factory ] ) 102 | ? $this->_factories[ $factory ] 103 | : null; 104 | } 105 | 106 | /** 107 | * Registers new terms factory. 108 | * 109 | * @since 1.0.0 110 | * 111 | * @access public 112 | * @param string $factory_name The factory name. 113 | * @param string $taxonomy The taxonomy name. 114 | * @return boolean TRUE on success, otherwise FALSE. 115 | */ 116 | public function addTermsFactory( $factory_name, $taxonomy ) { 117 | // do nothing if factory name is already taken 118 | if ( ! empty( $this->_factories[ $factory_name ] ) ) { 119 | return false; 120 | } 121 | 122 | // do nothing if a taxonomy doesn't exist 123 | if ( ! taxonomy_exists( $taxonomy ) ) { 124 | return false; 125 | } 126 | 127 | $this->_factories[ $factory_name ] = new TermsFactory( $taxonomy ); 128 | 129 | return true; 130 | } 131 | 132 | /** 133 | * Cleans up all factories. 134 | * 135 | * @since 1.0.0 136 | * 137 | * @access public 138 | */ 139 | public function cleanup() { 140 | foreach ( $this->_factories as $factory ) { 141 | if ( $factory instanceof \WPCC\Component\Factory ) { 142 | $factory->deleteAll(); 143 | } 144 | } 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/Blog.php: -------------------------------------------------------------------------------- 1 | $current_site->domain, 50 | 'path' => new Sequence( $base . 'testpath%s' ), 51 | 'title' => new FakerSequence( 'company' ), 52 | 'site_id' => $current_site->id, 53 | ) ); 54 | } 55 | 56 | /** 57 | * Generates a new blog. 58 | * 59 | * @since 1.0.0 60 | * 61 | * @access protected 62 | * @global \wpdb $wpdb The database connection. 63 | * @param array $args The array of arguments to use during a new blog creation. 64 | * @return int|\WP_Error The newly created blog's ID on success, otherwise a WP_Error object. 65 | */ 66 | protected function _createObject( $args ) { 67 | global $wpdb; 68 | 69 | $meta = isset( $args['meta'] ) ? $args['meta'] : array(); 70 | $user_id = isset( $args['user_id'] ) ? $args['user_id'] : get_current_user_id(); 71 | 72 | // temp tables will trigger db errors when we attempt to reference them as new temp tables 73 | $suppress = $wpdb->suppress_errors(); 74 | $blog = wpmu_create_blog( $args['domain'], $args['path'], $args['title'], $user_id, $meta, $args['site_id'] ); 75 | $wpdb->suppress_errors( $suppress ); 76 | 77 | if ( $blog && ! is_wp_error( $blog ) ) { 78 | $this->_debug( 'Generated blog ID: ' . $blog ); 79 | } elseif ( is_wp_error( $blog ) ) { 80 | $this->_debug( 81 | 'Blog generation failed with message [%s] %s', 82 | $blog->get_error_code(), 83 | $blog->get_error_messages() 84 | ); 85 | } else { 86 | $this->_debug( 'Blog generation failed' ); 87 | } 88 | 89 | return $blog; 90 | } 91 | 92 | /** 93 | * Does nothing, just implements abstract method. 94 | * 95 | * @since 1.0.0 96 | * 97 | * @access protected 98 | * @param mixed $blog_id The blog id. 99 | * @param array $fields The array of fields to update. 100 | * @return int The blog id. 101 | */ 102 | protected function _updateObject( $blog_id, $fields ) { 103 | return $blog_id; 104 | } 105 | 106 | /** 107 | * Deletes previously generated blog. 108 | * 109 | * @since 1.0.0 110 | * 111 | * @access protected 112 | * @global \wpdb $wpdb The database connection. 113 | * @param int $blog_id The blog id. 114 | * @return boolean Always returns TRUE. 115 | */ 116 | protected function _deleteObject( $blog_id ) { 117 | global $wpdb; 118 | 119 | $suppress = $wpdb->suppress_errors(); 120 | wpmu_delete_blog( $blog_id, true ); 121 | $wpdb->suppress_errors( $suppress ); 122 | 123 | $this->_debug( 'Deleted blog with ID: ' . $blog_id ); 124 | 125 | return true; 126 | } 127 | 128 | /** 129 | * Returns generated blog by id. 130 | * 131 | * @since 1.0.0 132 | * 133 | * @access public 134 | * @param int $blog_id The blog id. 135 | * @return object|boolean The generated blog details on success, otherwise FALSE. 136 | */ 137 | public function getObjectById( $blog_id ) { 138 | return get_blog_details( $blog_id, false ); 139 | } 140 | 141 | } -------------------------------------------------------------------------------- /classes/WPCC/TestLoader.php: -------------------------------------------------------------------------------- 1 | setBackupGlobals( false ); 49 | $testCase->setBackupStaticAttributes( false ); 50 | $testCase->setRunTestInSeparateProcess( false ); 51 | $testCase->setInIsolation( false ); 52 | $testCase->setPreserveGlobalState( false ); 53 | 54 | if ( $testCase instanceof Configurable ) { 55 | $testCase->configName( $name ); 56 | $testCase->configFile( $file ); 57 | $testCase->initConfig(); 58 | } 59 | } 60 | 61 | /** 62 | * Adds Cept test to the tests list. 63 | * 64 | * @sicne 1.0.0 65 | * 66 | * @access public 67 | * @param string $file The Cept test action name. 68 | */ 69 | public function addCept( $file ) { 70 | $name = $this->relativeName( $file ); 71 | 72 | $cept = new Cept(); 73 | $this->_setupTestCase( $cept, $name, $file ); 74 | 75 | $this->tests[] = $cept; 76 | } 77 | 78 | /** 79 | * Creates test from Cest method. 80 | * 81 | * @since 1.0.0 82 | * 83 | * @access protected 84 | * @param object $cestInstance The instance of a Cest class. 85 | * @param string $methodName The method name to create test from. 86 | * @param mixed $file Deprecated parameter. 87 | * @return \WPCC\TestCase\Cest Instance of Cest test. 88 | */ 89 | protected function createTestFromCestMethod( $cestInstance, $methodName, $file ) { 90 | if ( strpos( $methodName, '_' ) === 0 || $methodName == '__construct' ) { 91 | return null; 92 | } 93 | 94 | $cest = new Cest(); 95 | $cest->config( 'testClassInstance', $cestInstance ); 96 | $cest->config( 'testMethod', $methodName ); 97 | 98 | $this->_setupTestCase( $cest, $methodName, $file ); 99 | 100 | $testClass = get_class( $cestInstance ); 101 | $cest->getScenario()->env( Annotation::forMethod( $testClass, $methodName )->fetchAll( 'env' ) ); 102 | $cest->setDependencies( \PHPUnit_Util_Test::getDependencies( $testClass, $methodName ) ); 103 | 104 | return $cest; 105 | } 106 | 107 | /** 108 | * Creates test from PHPUnit method. 109 | * 110 | * @since 1.0.0 111 | * 112 | * @access protected 113 | * @param \ReflectionClass $class The class object. 114 | * @param \ReflectionMethod $method The method object. 115 | * @return \PHPUnit_Framework_Test The test object. 116 | */ 117 | protected function createTestFromPhpUnitMethod( \ReflectionClass $class, \ReflectionMethod $method ) { 118 | if ( ! \PHPUnit_Framework_TestSuite::isTestMethod( $method ) ) { 119 | return; 120 | } 121 | 122 | $test = \PHPUnit_Framework_TestSuite::createTest( $class, $method->name ); 123 | if ( $test instanceof \PHPUnit_Framework_TestSuite_DataProvider ) { 124 | foreach ( $test->tests() as $t ) { 125 | $this->enhancePhpunitTest( $t ); 126 | } 127 | } else { 128 | $this->enhancePhpunitTest( $test ); 129 | } 130 | 131 | $test->setBackupGlobals( false ); 132 | $test->setBackupStaticAttributes( false ); 133 | $test->setRunTestInSeparateProcess( false ); 134 | $test->setInIsolation( false ); 135 | $test->setPreserveGlobalState( false ); 136 | 137 | return $test; 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/Network.php: -------------------------------------------------------------------------------- 1 | domainName; 45 | 46 | parent::__construct( array( 47 | 'domain' => $domain, 48 | 'title' => new Sequence( 'Network %s' ), 49 | 'path' => new Sequence( '/testpath%s/' ), 50 | 'network_id' => new Sequence( '%s', 2 ), 51 | 'subdomain_install' => false, 52 | ) ); 53 | } 54 | 55 | /** 56 | * Generates a new network. 57 | * 58 | * @since 1.0.0 59 | * 60 | * @access protected 61 | * @param array $args The array of arguments to use during a new object creation. 62 | * @return int The newly created network's ID. 63 | */ 64 | protected function _createObject( $args ) { 65 | require_once ABSPATH . 'wp-admin/includes/upgrade.php'; 66 | 67 | $faker = \Faker\Factory::create(); 68 | $test_email = defined( 'WP_TESTS_EMAIL' ) ? WP_TESTS_EMAIL : $faker->email; 69 | 70 | $email = isset( $args['user'] ) 71 | ? get_userdata( $args['user'] )->user_email 72 | : $test_email; 73 | 74 | $network = populate_network( $args['network_id'], $args['domain'], $email, $args['title'], $args['path'], $args['subdomain_install'] ); 75 | 76 | if ( $network && ! is_wp_error( $network ) ) { 77 | $this->_debug( 'Generated network ID: ' . $args['network_id'] ); 78 | } elseif ( is_wp_error( $network ) ) { 79 | $this->_debug( 80 | 'Network generation failed with message [%s] %s', 81 | $network->get_error_code(), 82 | $network->get_error_messages() 83 | ); 84 | } else { 85 | $this->_debug( 'Network generation failed' ); 86 | } 87 | 88 | return $args['network_id']; 89 | } 90 | 91 | /** 92 | * Does nothing, just implements abstract method. 93 | * 94 | * @since 1.0.0 95 | * 96 | * @access protected 97 | * @param mixed $object The network id. 98 | * @param array $fields The array of fields to update. 99 | * @return int The network id. 100 | */ 101 | protected function _updateObject( $network_id, $fields ) { 102 | return $network_id; 103 | } 104 | 105 | /** 106 | * Deletes previously generated network. 107 | * 108 | * @since 1.0.0 109 | * 110 | * @access protected 111 | * @global \wpdb $wpdb The database connection. 112 | * @param int $network_id The network id. 113 | * @return boolean TRUE on success, otherwise FALSE. 114 | */ 115 | protected function _deleteObject( $network_id ) { 116 | global $wpdb; 117 | 118 | $network_blog = wp_get_sites( array( 'network_id' => $network_id ) ); 119 | if ( ! empty( $network_blog ) ) { 120 | $suppress = $wpdb->suppress_errors(); 121 | 122 | foreach ( $network_blog as $blog ) { 123 | wpmu_delete_blog( $blog->blog_id, true ); 124 | } 125 | 126 | $wpdb->suppress_errors( $suppress ); 127 | } 128 | 129 | $deleted = $wpdb->delete( $wpdb->site, array( 'id' => $network_id ), array( '%d' ) ); 130 | if ( $deleted ) { 131 | $wpdb->delete( $wpdb->sitemeta, array( 'site_id' => $network_id ), array( '%d' ) ); 132 | $this->_debug( 'Deleted network with ID: ' . $network_id ); 133 | 134 | return true; 135 | } 136 | 137 | $this->_debug( 'Failed to delet network with ID: ' . $network_id ); 138 | 139 | return false; 140 | } 141 | 142 | /** 143 | * Returns generated network by id. 144 | * 145 | * @since 1.0.0 146 | * 147 | * @access public 148 | * @param int $network_id The network id. 149 | * @return object|boolean The generated nework on success, otherwise FALSE. 150 | */ 151 | public function getObjectById( $network_id ) { 152 | return wp_get_network( $network_id ); 153 | } 154 | 155 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/Comment.php: -------------------------------------------------------------------------------- 1 | new FakerSequence( 'name' ), 45 | 'comment_author_url' => new FakerSequence( 'url' ), 46 | 'comment_approved' => 1, 47 | 'comment_content' => new FakerSequence( 'sentences' ), 48 | ) ); 49 | } 50 | 51 | /** 52 | * Generates a new comment. 53 | * 54 | * @since 1.0.0 55 | * 56 | * @access protected 57 | * @param array $args The array of arguments to use during a new comment creation. 58 | * @return int|boolean The newly created comment's ID on success, otherwise FALSE. 59 | */ 60 | protected function _createObject( $args ) { 61 | $comment_id = wp_insert_comment( $this->_addSlashesDeep( $args ) ); 62 | if ( $comment_id && ! is_wp_error( $comment_id ) ) { 63 | $this->_debug( 'Generated comment ID: ' . $comment_id ); 64 | } elseif ( is_wp_error( $comment_id ) ) { 65 | $this->_debug( 66 | 'Comment generation failed with message [%s] %s', 67 | $comment_id->get_error_code(), 68 | $comment_id->get_error_messages() 69 | ); 70 | } else { 71 | $this->_debug( 'Comment generation failed' ); 72 | } 73 | 74 | return $comment_id; 75 | } 76 | 77 | /** 78 | * Updates generated comment. 79 | * 80 | * @since 1.0.0 81 | * 82 | * @access protected 83 | * @param mixed $comment_id The comment id. 84 | * @param array $fields The array of fields to update. 85 | * @return boolean TRUE on success, otherwise FALSE. 86 | */ 87 | protected function _updateObject( $comment_id, $fields ) { 88 | $fields['comment_ID'] = $comment_id; 89 | $updated = (bool) wp_update_comment( $this->_addSlashesDeep( $fields ) ); 90 | if ( $updated ) { 91 | $this->_debug( 'Updated comment ' . $comment_id ); 92 | } else { 93 | $this->_debug( 'Update failed for comment ' . $comment_id ); 94 | } 95 | 96 | return $updated; 97 | } 98 | 99 | /** 100 | * Deletes previously generated comment. 101 | * 102 | * @since 1.0.0 103 | * 104 | * @access protected 105 | * @param int $comment_id The comment id. 106 | * @return boolean TRUE on success, otherwise FALSE. 107 | */ 108 | protected function _deleteObject( $comment_id ) { 109 | $comment = get_comment( $comment_id ); 110 | if ( ! $comment || is_wp_error( $comment ) ) { 111 | return false; 112 | } 113 | 114 | $deleted = wp_delete_comment( $comment_id, true ); 115 | if ( $deleted ) { 116 | $this->_debug( 'Deleted comment with ID: ' . $comment_id ); 117 | return true; 118 | } 119 | 120 | $this->_debug( 'Comment removal failed for ID: ' . $comment_id ); 121 | return false; 122 | } 123 | 124 | /** 125 | * Creates comments for a post. 126 | * 127 | * @since 1.0.0 128 | * 129 | * @access public 130 | * @param int $post_id The post id to create comments for. 131 | * @param int $count The number of comments to create. 132 | * @param array $args The array of arguments to use during a new object creation. 133 | * @param array $definitions Custom difinitions of default values for a new object properties. 134 | * @return array The array of generated comment ids. 135 | */ 136 | public function createPostComments( $post_id, $count = 1, $args = array(), $definitions = null ) { 137 | $args['comment_post_ID'] = $post_id; 138 | return $this->createMany( $count, $args, $definitions ); 139 | } 140 | 141 | /** 142 | * Returns generated comment by id. 143 | * 144 | * @since 1.0.0 145 | * 146 | * @access public 147 | * @param int $comment_id The comment id. 148 | * @return mixed The generated comment on success, otherwise NULL. 149 | */ 150 | public function getObjectById( $comment_id ) { 151 | return get_comment( $comment_id ); 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /wp-codeception.php: -------------------------------------------------------------------------------- 1 | getMessage() ); 57 | } 58 | } 59 | 60 | // Register WP-CLI commands 61 | WP_CLI::add_command( 'codeception', '\WPCC\CLI\Codeception' ); 62 | WP_CLI::add_command( 'selenium', '\WPCC\CLI\Selenium' ); 63 | 64 | } 65 | 66 | /** 67 | * Following functions were copied from the Codeception's autoload file, because 68 | * we are not going to load it anymore, but these functions are still required 69 | * and used in the depths of the framework, so we have to load it here. 70 | */ 71 | 72 | if ( ! function_exists( 'codecept_debug' ) ) : 73 | 74 | /** 75 | * Registers debug message which will be printed if --debug argument is passed to the command. 76 | * 77 | * @since 1.0.1 78 | * 79 | * @param mixed $data The debug data, it will be serialized if we need to display it. 80 | */ 81 | function codecept_debug( $data ) { 82 | \Codeception\Util\Debug::debug( $data ); 83 | } 84 | 85 | endif; 86 | 87 | if ( ! function_exists( 'codecept_root_dir' ) ) : 88 | 89 | /** 90 | * Returns absolute path to the requested object which is expected to be in 91 | * the root directory of a testing project. 92 | * 93 | * @since 1.0.1 94 | * 95 | * @param string $appendPath A relative path to the requested object. 96 | * @return string The absolute path to the object. 97 | */ 98 | function codecept_root_dir( $appendPath = '' ) { 99 | return \Codeception\Configuration::projectDir() . $appendPath; 100 | } 101 | 102 | endif; 103 | 104 | if ( ! function_exists( 'codecept_output_dir' ) ) : 105 | 106 | /** 107 | * Returns absolute path to the requested object which is expected to be in 108 | * the output directory of a testing project. 109 | * 110 | * @since 1.0.1 111 | * 112 | * @param string $appendPath A relative path to the requested object. 113 | * @return string The absolute path to the object. 114 | */ 115 | function codecept_output_dir( $appendPath = '' ) { 116 | return \Codeception\Configuration::outputDir() . $appendPath; 117 | } 118 | 119 | endif; 120 | 121 | if ( ! function_exists( 'codecept_log_dir' ) ) : 122 | 123 | /** 124 | * Returns absolute path to the requested object which is expected to be in 125 | * the log directory of a testing project. 126 | * 127 | * @since 1.0.1 128 | * 129 | * @param string $appendPath A relative path to the requested object. 130 | * @return string The absolute path to the object. 131 | */ 132 | function codecept_log_dir( $appendPath = '' ) { 133 | return \Codeception\Configuration::outputDir() . $appendPath; 134 | } 135 | 136 | endif; 137 | 138 | if ( ! function_exists( 'codecept_data_dir' ) ) : 139 | 140 | /** 141 | * Returns absolute path to the requested object which is expected to be in 142 | * the data directory of a testing project. 143 | * 144 | * @since 1.0.1 145 | * 146 | * @param string $appendPath A relative path to the requested object. 147 | * @return string The absolute path to the object. 148 | */ 149 | function codecept_data_dir( $appendPath = '' ) { 150 | return \Codeception\Configuration::dataDir() . $appendPath; 151 | } 152 | 153 | endif; 154 | -------------------------------------------------------------------------------- /classes/WPCC/Command/Run.php: -------------------------------------------------------------------------------- 1 | options = $input->getOptions(); 49 | $this->output = $output; 50 | 51 | $config = Configuration::config( $this->options['config'] ); 52 | 53 | if ( ! $this->options['colors'] ) { 54 | $this->options['colors'] = $config['settings']['colors']; 55 | } 56 | 57 | if ( ! $this->options['silent'] ) { 58 | $this->output->writeln( Codecept::versionString() . "\nPowered by " . \PHPUnit_Runner_Version::getVersionString() ); 59 | } 60 | 61 | if ( $this->options['debug'] ) { 62 | $this->output->setVerbosity( OutputInterface::VERBOSITY_VERY_VERBOSE ); 63 | } 64 | 65 | $userOptions = array_intersect_key( $this->options, array_flip( $this->passedOptionKeys( $input ) ) ); 66 | $userOptions = array_merge( $userOptions, $this->booleanOptions( $input, ['xml', 'html', 'json', 'tap', 'coverage', 'coverage-xml', 'coverage-html' ] ) ); 67 | $userOptions['verbosity'] = $this->output->getVerbosity(); 68 | $userOptions['interactive'] = ! $input->hasParameterOption( array( '--no-interaction', '-n' ) ); 69 | 70 | if ( $this->options['no-colors'] ) { 71 | $userOptions['colors'] = false; 72 | } 73 | if ( $this->options['group'] ) { 74 | $userOptions['groups'] = $this->options['group']; 75 | } 76 | if ( $this->options['skip-group'] ) { 77 | $userOptions['excludeGroups'] = $this->options['skip-group']; 78 | } 79 | if ( $this->options['report'] ) { 80 | $userOptions['silent'] = true; 81 | } 82 | if ( $this->options['coverage-xml'] || $this->options['coverage-html'] || $this->options['coverage-text'] ) { 83 | $this->options['coverage'] = true; 84 | } 85 | 86 | $suite = $input->getArgument( 'suite' ); 87 | $test = $input->getArgument( 'test' ); 88 | 89 | if ( ! Configuration::isEmpty() && ! $test && strpos( $suite, $config['paths']['tests'] ) === 0 ) { 90 | list( $matches, $suite, $test ) = $this->matchTestFromFilename( $suite, $config['paths']['tests'] ); 91 | } 92 | 93 | if ( $this->options['group'] ) { 94 | $this->output->writeln( sprintf( "[Groups] %s ", implode( ', ', $this->options['group'] ) ) ); 95 | } 96 | if ( $input->getArgument( 'test' ) ) { 97 | $this->options['steps'] = true; 98 | } 99 | 100 | if ( $test ) { 101 | $filter = $this->matchFilteredTestNameEx( $test ); 102 | $userOptions['filter'] = $filter; 103 | } 104 | 105 | $this->codecept = new Codecept( $userOptions ); 106 | 107 | if ( $suite && $test ) { 108 | $this->codecept->run( $suite, $test ); 109 | } 110 | 111 | if ( ! $test ) { 112 | $suites = $suite ? explode( ',', $suite ) : Configuration::suites(); 113 | $this->executed = $this->runSuites( $suites, $this->options['skip'] ); 114 | 115 | if ( !empty( $config['include'] ) ) { 116 | $current_dir = Configuration::projectDir(); 117 | $suites += $config['include']; 118 | $this->runIncludedSuites( $config['include'], $current_dir ); 119 | } 120 | 121 | if ( $this->executed === 0 ) { 122 | throw new \RuntimeException( 123 | sprintf( "Suite '%s' could not be found", implode( ', ', $suites ) ) 124 | ); 125 | } 126 | } 127 | 128 | $this->codecept->printResult(); 129 | 130 | if ( ! $input->getOption( 'no-exit' ) ) { 131 | if ( ! $this->codecept->getResult()->wasSuccessful() ) { 132 | exit( 1 ); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Overriden version of private function. 139 | * 140 | * @since 1.0.0 141 | * 142 | * @access protected 143 | * @param string $path The test name. 144 | * @return string The filtered test name on success, otherwise NULL. 145 | */ 146 | protected function matchFilteredTestNameEx( &$path ) { 147 | $test_parts = explode( ':', $path ); 148 | if ( count( $test_parts ) > 1 ) { 149 | list( $path, $filter ) = $test_parts; 150 | return $filter; 151 | } 152 | 153 | return null; 154 | } 155 | 156 | } -------------------------------------------------------------------------------- /classes/WPCC/Module/Wpdb.php: -------------------------------------------------------------------------------- 1 | _wpdb = $wpdb; 55 | } 56 | 57 | /** 58 | * Builds query. 59 | * 60 | * @since 1.0.0 61 | * 62 | * @access protected 63 | * @param string $table The table name. 64 | * @param array|string $columns The array of columns to select. 65 | * @param array $criteria The array of conditions. 66 | * @return string The query string. 67 | */ 68 | protected function _prepareQuery( $table, $columns, $criteria ) { 69 | $where = '1 = 1'; 70 | $params = array(); 71 | 72 | foreach ( $criteria as $column => $value ) { 73 | $pattern = '%s'; 74 | if ( is_null( $value ) ) { 75 | $pattern = '%s AND `%s` IS NULL'; 76 | } elseif ( is_numeric( $value ) ) { 77 | $pattern = '%s AND `%s` = %%d'; 78 | $params[] = $value; 79 | } else { 80 | $pattern = '%s AND `%s` = %%s'; 81 | $params[] = $value; 82 | } 83 | 84 | $where = sprintf( $pattern, $where, $column ); 85 | } 86 | 87 | if ( is_array( $columns ) ) { 88 | $columns = implode( ', ', $columns ); 89 | } 90 | 91 | $query = sprintf( 'SELECT %s FROM %s WHERE %s', $columns, $table, $where ); 92 | if ( ! empty( $params ) ) { 93 | $query = $this->_wpdb->prepare( $query, $params ); 94 | } 95 | 96 | return $query; 97 | } 98 | 99 | /** 100 | * Checks whether or not a record exists in the database. 101 | * 102 | * @since 1.0.0 103 | * 104 | * @access public 105 | * @param string $table The table name. 106 | * @param array $criteria The array of conditions. 107 | */ 108 | public function seeInDatabase( $table, $criteria = array() ) { 109 | $query = $this->_prepareQuery( $table, 'count(*)', $criteria ); 110 | $this->debugSection( 'Query', $query ); 111 | 112 | $suppress_errors = $this->_wpdb->suppress_errors( true ); 113 | $res = $this->_wpdb->get_var( $query ); 114 | $this->_wpdb->suppress_errors( $suppress_errors ); 115 | 116 | if ( ! empty( $this->_wpdb->last_error ) ) { 117 | $this->fail( 'Database error: ' . $this->_wpdb->last_error ); 118 | return; 119 | } 120 | 121 | $this->assertGreaterThan( 0, $res, 'No matching records found' ); 122 | } 123 | 124 | /** 125 | * Checks whether or not a record doesn't exist in the database. 126 | * 127 | * @since 1.0.0 128 | * 129 | * @access public 130 | * @param string $table The table name. 131 | * @param array $criteria The array of conditions. 132 | */ 133 | public function dontSeeInDatabase( $table, $criteria = array() ) { 134 | $query = $this->_prepareQuery( $table, 'count(*)', $criteria ); 135 | $this->debugSection( 'Query', $query ); 136 | 137 | $suppress_errors = $this->_wpdb->suppress_errors( true ); 138 | $res = $this->_wpdb->get_var( $query ); 139 | $this->_wpdb->suppress_errors( $suppress_errors ); 140 | 141 | if ( ! empty( $this->_wpdb->last_error ) ) { 142 | $this->fail( 'Database error: ' . $this->_wpdb->last_error ); 143 | return; 144 | } 145 | 146 | $this->assertLessThan( 1, $res, 'Matching records found' ); 147 | } 148 | 149 | /** 150 | * Fetches rows from database. 151 | * 152 | * @since 1.0.0 153 | * 154 | * @access public 155 | * @param string $table The table name. 156 | * @param array|string $columns The array of columns to select. 157 | * @param array $criteria The array of conditions. 158 | * @return array The array of fetched rows. 159 | */ 160 | public function grabFromDatabase( $table, $columns, $criteria = array() ) { 161 | $query = $this->_prepareQuery( $table, $columns, $criteria ); 162 | $this->debugSection( 'Query', $query ); 163 | 164 | $suppress_errors = $this->_wpdb->suppress_errors( true ); 165 | $results = $this->_wpdb->get_results( $query ); 166 | $this->_wpdb->suppress_errors( $suppress_errors ); 167 | 168 | if ( ! empty( $this->_wpdb->last_error ) ) { 169 | $this->fail( 'Database error: ' . $this->_wpdb->last_error ); 170 | return; 171 | } 172 | 173 | return $results; 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory/Term.php: -------------------------------------------------------------------------------- 1 | _taxonomy = $taxonomy ?: self::DEFAULT_TAXONOMY; 58 | 59 | parent::__construct( array( 60 | 'name' => new Sequence( 'Term %s' ), 61 | 'taxonomy' => $this->_taxonomy, 62 | 'description' => new FakerSequence( 'paragraph' ), 63 | ) ); 64 | } 65 | 66 | /** 67 | * Generates a new term. 68 | * 69 | * @since 1.0.0 70 | * 71 | * @access protected 72 | * @param array $args The array of arguments to use during a new term creation. 73 | * @return int|\WP_Error The newly created term's ID on success, otherwise a WP_Error object. 74 | */ 75 | protected function _createObject( $args ) { 76 | $args = array_merge( array( 'taxonomy' => $this->_taxonomy ), $args ); 77 | $term_id_pair = wp_insert_term( $args['name'], $args['taxonomy'], $args ); 78 | if ( is_wp_error( $term_id_pair ) ) { 79 | $this->_debug( 80 | 'Term generation failed with message [%s] %s', 81 | $term_id_pair->get_error_code(), 82 | $term_id_pair->get_error_messages() 83 | ); 84 | 85 | return $term_id_pair; 86 | } 87 | 88 | $this->_debug( 'Generated term ID: ' . $term_id_pair['term_id'] ); 89 | 90 | return $term_id_pair['term_id']; 91 | } 92 | 93 | /** 94 | * Updates generated term. 95 | * 96 | * @since 1.0.0 97 | * 98 | * @access protected 99 | * @param mixed $term The term id to update. 100 | * @param array $fields The array of fields to update. 101 | * @return mixed Updated term ID on success, otherwise a WP_Error object. 102 | */ 103 | protected function _updateObject( $term, $fields ) { 104 | $fields = array_merge( array( 'taxonomy' => $this->_taxonomy ), $fields ); 105 | $taxonomy = is_object( $term ) ? $term->taxonomy : $this->_taxonomy; 106 | 107 | $term_id_pair = wp_update_term( $term, $taxonomy, $fields ); 108 | if ( is_wp_error( $term_id_pair ) ) { 109 | $this->_debug( 110 | 'Update failed for term %d with message [%s] %s', 111 | is_object( $term ) ? $term->term_id : $term, 112 | $term_id_pair->get_error_code(), 113 | $term_id_pair->get_error_message() 114 | ); 115 | 116 | return $term_id_pair; 117 | } 118 | 119 | $this->_debug( 'Updated term ' . $term_id_pair['term_id'] ); 120 | 121 | return $term_id_pair['term_id']; 122 | } 123 | 124 | /** 125 | * Deletes previously generated term. 126 | * 127 | * @since 1.0.0 128 | * 129 | * @access protected 130 | * @param int $term_id The term id to delete. 131 | * @return boolean TRUE on success, otherwise FALSE. 132 | */ 133 | protected function _deleteObject( $term_id ) { 134 | $term = get_term_by( 'id', $term_id, $this->_taxonomy ); 135 | if ( ! $term || is_wp_error( $term ) ) { 136 | return false; 137 | } 138 | 139 | $deleted = wp_delete_term( $term_id, $this->_taxonomy ); 140 | if ( $deleted && ! is_wp_error( $deleted ) ) { 141 | $this->_debug( 'Deleted term with ID: ' . $term_id ); 142 | return true; 143 | } elseif ( is_wp_error( $deleted ) ) { 144 | $this->_debug( 145 | 'Removal failed for term %d with message [%s] %s', 146 | $term_id, 147 | $deleted->get_error_code(), 148 | $deleted->get_error_message() 149 | ); 150 | } else { 151 | $this->_debug( 'Term removal failed for ID: ' . $term_id ); 152 | } 153 | 154 | return false; 155 | } 156 | 157 | /** 158 | * Adds terms to a post. 159 | * 160 | * @since 1.0.0 161 | * 162 | * @access public 163 | * @param int $post_id The post id to add terms to. 164 | * @param array $terms The array of terms to add. 165 | * @param string $taxonomy The taxonomy name of terms. 166 | * @param boolean $append Determines whether to add or replace terms. 167 | * @return array The array of affected term IDs. 168 | */ 169 | public function addPostTerms( $post_id, $terms, $taxonomy, $append = true ) { 170 | return wp_set_post_terms( $post_id, $terms, $taxonomy, $append ); 171 | } 172 | 173 | /** 174 | * Returns generated term by id. 175 | * 176 | * @since 1.0.0 177 | * 178 | * @access public 179 | * @param int $term_id The term id. 180 | * @return mixed The generated term on success, otherwise NULL or a WP_Error object. 181 | */ 182 | public function getObjectById( $term_id ) { 183 | return get_term( $term_id, $this->_taxonomy ); 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Codeception 2 | 3 | > This is a WordPress Plugin which integrates with the [Codeception](http://codeception.com/) PHP testing framework and allows you to write and run Codeception tests for WordPress via WP CLI. 4 | 5 | [![Support Level](https://img.shields.io/badge/support-archived-red.svg)](#support-level) 6 | 7 | We're working towards supporting all of [Codeceptions commands](http://codeception.com/docs/reference/Commands). If you find one we haven't included yet, please submit a [Pull Request](https://github.com/10up/wp-codeception/pulls)! 8 | 9 | ## Installation 10 | [Download the latest version](https://github.com/10up/wp-codeception/archive/master.zip) and extract, or clone the repository with Git into a new directory `wp-content/plugins/wp-codeception` in your WordPress install. 11 | 12 | #### Install required node modules and composer dependencies 13 | 14 | We'll run our commands from within [VVV](https://github.com/Varying-Vagrant-Vagrants/VVV) because WP CLI, Node, and Composer are already installed for us there. 15 | 16 | ```Bash 17 | $ vagrant up 18 | $ vagrant ssh 19 | $ sudo apt-get install openjdk-7-jre-headless 20 | $ cd /srv/www/yoursite/htdocs/wp-content/plugins/wp-codeception 21 | $ composer install 22 | $ wp plugin activate wp-codeception 23 | ``` 24 | 25 | Afterwards you'll have a new `vendor` directory within your `plugins/wp-codeception` directory which contains all the code libraries we're dependant on. 26 | 27 | #### Install as a composer dependency 28 | 29 | There is an alternative way to install this plugin. You can add it as a composer dependency for your project. To do it, run following command in your terminal: 30 | 31 | ```Bash 32 | $ composer require 10up/wp-codeception 33 | ``` 34 | 35 | This command will install the plugin and all its dependencies for your project. Please, pay attention that if you already use [composer/installers](https://github.com/composer/installers) dependency in your project, then `wp-codeception` will be installed into `/wp-content/plugins/wp-codeception/` folder. It happens, because `wp-codeception` has `wordpress-plugin` type and will be processed by `composer/installers` accordingly (read its documentation for more details). 36 | 37 | If you want to add it as a dependency to your plugin or theme, you will need to update your `composer.json` file and tell it where to install `wp-codeception`. You can achieve it by providing `installer-paths` instructions like in the snippet below. 38 | 39 | ```Bash 40 | { 41 | ..., 42 | "extra": { 43 | "installer-paths": { 44 | "vendor/{$name}/": ["type:wordpress-plugin"] 45 | } 46 | }, 47 | ... 48 | } 49 | ``` 50 | 51 | Now `composer/installers` will know to install wordpress plugins into *vendor* folder. The final step which you need to do is to update your `autoload` section and add `wp-codeception.php` file to the autoload files list. 52 | 53 | ```Bash 54 | { 55 | ..., 56 | "autoload": { 57 | "psr-X": { 58 | ... 59 | }, 60 | "files": [ 61 | ..., 62 | "vendor/wp-codeception/wp-codeception.php" 63 | ] 64 | }, 65 | ... 66 | } 67 | ``` 68 | 69 | #### Install the test suite 70 | 71 | See the [Codeception bootstrapping documentation](http://codeception.com/docs/reference/Commands#Bootstrap) for further information. 72 | 73 | ```Bash 74 | # You'll create the test suite in your own plugin or theme directory.. 75 | $ cd /srv/www/yoursite/htdocs/wp-content/{your plugin or theme directory} 76 | $ wp codeception bootstrap 77 | ``` 78 | 79 | Afterwards you'll have a new `tests` directory within your plugin or theme directory. This is your new test suite, and where you'll write all your tests. 80 | 81 | ## Writing Tests 82 | You can write tests using any of the three [Codeception](http://codeception.com/) testing frameworks: [Acceptance](http://codeception.com/docs/03-AcceptanceTests), [Functional](http://codeception.com/docs/04-FunctionalTests) and [Unit](http://codeception.com/docs/05-UnitTests) testing. If you look within the new `tests` directory you'll see three config files; one for each test framework (acceptance.suite.yml, functional.suite.yml, unit.suite.yml). Edit these files as you wish. 83 | 84 | #### Generate your first test 85 | ```Bash 86 | # You should be in the plugin or theme directory where you ran the bootstrap 87 | $ wp codeception generate-(cept|cest) (acceptance|functional|unit) MyTestName 88 | 89 | # Example 90 | $ wp codeception generate-cept acceptance LoginTest 91 | ``` 92 | 93 | Afterwards you'll have a new file in your plugin or theme directory `tests/acceptance/LoginTest.php`, where you can write your first test. Remember, any Codeception test will work here! For example, you could run any of the [acceptance test examples](http://codeception.com/docs/03-AcceptanceTests) mentioned in the Codeception documentation. Likewise, the same goes for [Functional](http://codeception.com/docs/04-FunctionalTests) and [Unit tests](http://codeception.com/docs/05-UnitTests). 94 | 95 | #### Example: Writing a Login Acceptance Test 96 | ```PHP 97 | wantTo( 'Ensure WordPress Login Works' ); 103 | 104 | // Let's start on the login page 105 | $I->amOnPage( wp_login_url() ); 106 | 107 | // Populate the login form's user id field 108 | $I->fillField( 'input#user_login', 'YourUsername' ); 109 | 110 | // Popupate the login form's password field 111 | $I->fillField( 'input#user_pass', 'YourPassword' ); 112 | 113 | // Submit the login form 114 | $I->click( 'Log In' ); 115 | 116 | // Validate the successful loading of the Dashboard 117 | $I->see( 'Dashboard' ); 118 | ``` 119 | 120 | ## Running Your Tests 121 | Now you've written some tests, it's time to run them! But first.. 122 | 123 | #### Selenium 124 | If you've created any browser automation/acceptance tests you'll need to turn [Selenium](http://www.seleniumhq.org/) on, and likewise, you'll want to stop Selenium after you're through running tests. 125 | 126 | ```Bash 127 | # You can run these commands from anywhere in your WordPress install 128 | $ wp selenium start 129 | 130 | # Stop Selenium when you're through 131 | $ wp selenium stop 132 | ``` 133 | 134 | #### Run 135 | You'll use the `run` command to execute your tests from within your plugin or theme directory (where you ran the bootstrap). We've implemented most of the [Codeception 'run' command](http://codeception.com/docs/reference/Commands#Run) arguments, but if you find one we've missed please submit a [Pull Request](https://github.com/10up/wp-codeception/pulls)! 136 | 137 | ```Bash 138 | # You should be in the plugin or theme directory where you ran the bootstrap 139 | $ wp codeception run 140 | ``` 141 | 142 | #### Example: Running our Login Test 143 | ```Bash 144 | # You should be in the plugin or theme directory where you ran the bootstrap 145 | # Let's display verbose output 146 | $ wp codeception run -vvv 147 | 148 | Codeception PHP Testing Framework v2.0.11 149 | Powered by PHPUnit 4.5.1 by Sebastian Bergmann and contributors. 150 | 151 | Rebuilding AcceptanceTester... 152 | 153 | Acceptance-production Tests (1) --------------------------------- 154 | Modules: WebDriver, WordPress, AcceptanceHelper 155 | ----------------------------------------------------------------- 156 | Ensure WordPress Login Works (LoginTest) 157 | Scenario: 158 | * I am on page "http://site.com/wp-login.php" 159 | * I fill field "input#user_login","YourUsername" 160 | * I fill field "input#user_pass","YourPassword" 161 | * I click "Login" 162 | * I see "Dashboard" 163 | PASSED 164 | ``` 165 | 166 | ## Support Level 167 | 168 | **Archived:** This project is no longer maintained by 10up. We are no longer responding to Issues or Pull Requests unless they relate to security concerns. We encourage interested developers to fork this project and make it their own! 169 | 170 | ## Like what you see? 171 | 172 |

173 | 174 |

175 | -------------------------------------------------------------------------------- /classes/WPCC/Module/MailtrapIO.php: -------------------------------------------------------------------------------- 1 | '2525', 59 | ); 60 | 61 | /** 62 | * The current email data. 63 | * 64 | * @since 1.0.0 65 | * 66 | * @access protected 67 | * @var array 68 | */ 69 | protected $_current_email = array(); 70 | 71 | /** 72 | * Setups module environment. 73 | * 74 | * @since 1.0.0 75 | * 76 | * @access public 77 | */ 78 | public function _initialize() { 79 | add_action( 'phpmailer_init', array( $this, '_setup_smtp_settings' ) ); 80 | } 81 | 82 | /** 83 | * Setups SMPT settings for the mailer object. 84 | * 85 | * @since 1.0.0 86 | * @action phpmailer_init 87 | * 88 | * @access public 89 | * @param \PHPMailer $phpmailer The mailer object. 90 | */ 91 | public function _setup_smtp_settings( \PHPMailer $phpmailer ) { 92 | $phpmailer->Host = 'mailtrap.io'; 93 | $phpmailer->Port = $this->config['port']; 94 | $phpmailer->Username = $this->config['username']; 95 | $phpmailer->Password = $this->config['password']; 96 | $phpmailer->SMTPAuth = true; 97 | $phpmailer->SMTPDebug = 1; 98 | 99 | $phpmailer->IsSMTP(); 100 | } 101 | 102 | /** 103 | * Sends request to the Mailtrap.io API. 104 | * 105 | * @since 1.0.0 106 | * 107 | * @access protected 108 | * @param string $url The relative URL to a resource. 109 | * @param array $args The array of arguments for a request. 110 | * @return array The response array. 111 | */ 112 | protected function _send_request( $url, $args = array() ) { 113 | $url = 'https://mailtrap.io/api/v1/inboxes/' . $this->config['inbox_id'] . $url; 114 | 115 | $args = wp_parse_args( $args, array( 116 | 'method' => 'GET', 117 | 'headers' => array(), 118 | ) ); 119 | 120 | $args['headers'] = array_merge( $args['headers'], array( 121 | 'Api-Token' => $this->config['api_token'], 122 | 'Accept' => 'application/json', 123 | ) ); 124 | 125 | $response = wp_remote_request( $url, $args ); 126 | 127 | return $response; 128 | } 129 | 130 | /** 131 | * Sends GET request to the Mailtrap.io API. 132 | * 133 | * @since 1.0.0 134 | * 135 | * @access public 136 | * @param string $url The relative URL to a resource. 137 | * @return array The response array. 138 | */ 139 | protected function _send_get_request( $url ) { 140 | $response = $this->_send_request( $url, array( 'method' => 'GET' ) ); 141 | 142 | $response_code = wp_remote_retrieve_response_code( $response ); 143 | $this->assertEquals( 200, $response_code, 'The Mailtrap.io API resonse code is not equals to 200.' ); 144 | 145 | return $response; 146 | } 147 | 148 | /** 149 | * Sends DELETE request to the Mailtrap.io API. 150 | * 151 | * @since 1.0.0 152 | * 153 | * @access protected 154 | * @param string $url The relative URL to a resource. 155 | * @return array The response array. 156 | */ 157 | protected function _send_delete_request( $url ) { 158 | $response = $this->_send_request( $url, array( 'method' => 'DELETE' ) ); 159 | 160 | $response_code = wp_remote_retrieve_response_code( $response ); 161 | $this->assertEquals( 200, $response_code, 'The Mailtrap.io API resonse code is not equals to 200.' ); 162 | 163 | return $response; 164 | } 165 | 166 | /** 167 | * Sends PATCH request to the Mailtrap.io API. 168 | * 169 | * @since 1.0.0 170 | * 171 | * @access protected 172 | * @param string $url The relative URL to a resource. 173 | * @param array $body The request body. 174 | * @return array The response array. 175 | */ 176 | protected function _send_patch_request( $url, $body ) { 177 | $response = $this->_send_request( $url, array( 178 | 'method' => 'PATCH', 179 | 'body' => $body, 180 | ) ); 181 | 182 | $response_code = wp_remote_retrieve_response_code( $response ); 183 | $this->assertEquals( 200, $response_code, 'The Mailtrap.io API resonse code is not equals to 200.' ); 184 | 185 | return $response; 186 | } 187 | 188 | /** 189 | * Checks whether a new email exists for a recipient or not. 190 | * 191 | * @since 1.0.0 192 | * 193 | * @access public 194 | * @param string $recipient The email address of a recipient. 195 | */ 196 | public function seeNewEmailFor( $recipient ) { 197 | $email = $this->grabLatestEmailFor( $recipient ); 198 | $this->assertEquals( $recipient, $email['to_email'], 'The email recipient is wrong.' ); 199 | $this->assertFalse( $email['is_read'], 'The email is already read.' ); 200 | } 201 | 202 | /** 203 | * Grabs latest email for a recipient. 204 | * 205 | * @since 1.0.0 206 | * 207 | * @access public 208 | * @param string $recipient The email address of a recipient. 209 | * @return array The email array. 210 | */ 211 | public function grabLatestEmailFor( $recipient ) { 212 | $response = $this->_send_get_request( '/messages?search=' . urlencode( $recipient ) ); 213 | 214 | $emails = json_decode( wp_remote_retrieve_body( $response ), true ); 215 | $this->assertNotEmpty( $emails, 'Received emails array is empty.' ); 216 | 217 | $email = current( $emails ); 218 | 219 | return $email; 220 | } 221 | 222 | /** 223 | * Deletes an email from the inbox. If no email id is provided, then email id 224 | * will be taken from the current email. 225 | * 226 | * @since 1.0.0 227 | * 228 | * @access public 229 | * @param int $email_id The email id to delete. 230 | */ 231 | public function deleteEmail( $email_id = null ) { 232 | if ( ! $email_id ) { 233 | $this->assertNotEmpty( $this->_current_email, 'The current email is not selected' ); 234 | $email_id = $this->_current_email['id']; 235 | } 236 | 237 | $this->_send_delete_request( '/messages/' . $email_id ); 238 | } 239 | 240 | /** 241 | * Marks email read. If no email id is provided, then email id will be taken 242 | * from the current email. 243 | * 244 | * @since 1.0.0 245 | * 246 | * @access public 247 | * @param int $email_id The email id. 248 | */ 249 | public function markEmailRead( $email_id = null ) { 250 | if ( ! $email_id ) { 251 | $this->assertNotEmpty( $this->_current_email, 'The current email is not selected' ); 252 | $email_id = $this->_current_email['id']; 253 | } 254 | 255 | $this->_send_patch_request( '/messages/' . $email_id, array( 256 | 'message' => array( 'is_read' => true ) 257 | ) ); 258 | } 259 | 260 | /** 261 | * Selects latest email for a recipient. 262 | * 263 | * @since 1.0.0 264 | * 265 | * @access public 266 | * @param string $recipient The recipient email. 267 | */ 268 | public function amOnLatestEmailFor( $recipient ) { 269 | $this->_current_email = $this->grabLatestEmailFor( $recipient ); 270 | } 271 | 272 | /** 273 | * Checks whether email is not read yet or not. 274 | * 275 | * @since 1.0.0 276 | * 277 | * @access public 278 | */ 279 | public function seeEmailIsNotRead() { 280 | $this->assertFalse( $this->_current_email['is_read'], 'The email is already read.' ); 281 | } 282 | 283 | /** 284 | * Checks whether email is read or not. 285 | * 286 | * @since 1.0.0 287 | * 288 | * @access public 289 | */ 290 | public function seeEmailIsRead() { 291 | $this->assertTrue( $this->_current_email['is_read'], 'The email is not read yet.' ); 292 | } 293 | 294 | /** 295 | * Checks email subject to be equal to expected subject. 296 | * 297 | * @since 1.0.0 298 | * 299 | * @access public 300 | * @param string $subject The expected subject. 301 | */ 302 | public function seeEmailSubjectIs( $subject ) { 303 | $this->assertEquals( $subject, $this->_current_email['subject'], 'The email subject is wrong.' ); 304 | } 305 | 306 | /** 307 | * Checks whether sender email is equal to expected email. 308 | * 309 | * @since 1.0.0 310 | * 311 | * @access public 312 | * @param string $from_email The sender email address. 313 | */ 314 | public function seeEmailIsFrom( $from_email ) { 315 | $this->assertEquals( $from_email, $this->_current_email['from_email'], 'Sender email is not equal to expected email.' ); 316 | } 317 | 318 | /** 319 | * Checks whether recipient email is equal to expected email. 320 | * 321 | * @since 1.0.0 322 | * 323 | * @access public 324 | * @param string $to_email The recipient email address. 325 | */ 326 | public function seeEmailIsFor( $to_email ) { 327 | $this->assertEquals( $to_email, $this->_current_email['to_email'], 'Recipient email is not equal to expected email.' ); 328 | } 329 | 330 | } -------------------------------------------------------------------------------- /classes/WPCC/Component/Factory.php: -------------------------------------------------------------------------------- 1 | _definitions = $definitions; 68 | } 69 | 70 | /** 71 | * Displays debug message. 72 | * 73 | * @since 1.0.2 74 | * 75 | * @access protected 76 | * @param string $message Debug message. 77 | * @param array $args Debug arguments. 78 | */ 79 | protected function _debug( $message, $args = null ) { 80 | $message = '[Factory] ' . $message; 81 | 82 | if ( func_num_args() == 1 ) { 83 | Debug::debug( $message ); 84 | } else { 85 | $args = array_slice( func_get_args(), 1 ); 86 | Debug::debugf( $message, $args ); 87 | } 88 | } 89 | 90 | /** 91 | * Generates a new object. 92 | * 93 | * @since 1.0.0 94 | * 95 | * @abstract 96 | * @access protected 97 | * @param array $args The array of arguments to use during a new object creation. 98 | * @return object|boolean|\WP_Error A new object on success, otherwise FALSE or instance of WP_Error. 99 | */ 100 | protected abstract function _createObject( $args ); 101 | 102 | /** 103 | * Updates generated object. 104 | * 105 | * @since 1.0.0 106 | * 107 | * @abstract 108 | * @access protected 109 | * @param mixed $object The generated object. 110 | * @param array $fields The array of fields to update. 111 | * @return mixed Updated object on success, otherwise FALSE, 0 or a WP_Error object. 112 | */ 113 | protected abstract function _updateObject( $object, $fields ); 114 | 115 | /** 116 | * Deletes an object. 117 | * 118 | * @since 1.0.0 119 | * 120 | * @abstract 121 | * @access protected 122 | * @param mixed $object The generated object. 123 | * @return boolean|\WP_Error TRUE on success, otherwise FALSE or WP_Error object. 124 | */ 125 | protected abstract function _deleteObject( $object ); 126 | 127 | /** 128 | * Returns generated object by id. 129 | * 130 | * @since 1.0.0 131 | * 132 | * @abstract 133 | * @access public 134 | * @param int $object_id The object id. 135 | * @return mixed The generated object. 136 | */ 137 | public abstract function getObjectById( $object_id ); 138 | 139 | /** 140 | * Creates a new object. 141 | * 142 | * @since 1.0.0 143 | * 144 | * @access public 145 | * @param array $args The array of arguments to use during a new object creation. 146 | * @param array $definitions Custom difinitions of default values for a new object properties. 147 | * @return object|boolean|\WP_Error A new object identifier on success, otherwise FALSE or a WP_Error object. 148 | */ 149 | public function create( $args = array(), $definitions = null ) { 150 | if ( is_null( $definitions ) ) { 151 | $definitions = $this->_definitions; 152 | } 153 | 154 | $callbacks = array(); 155 | $generated_args = $this->_generateArgs( $args, $definitions, $callbacks ); 156 | if ( is_wp_error( $generated_args ) ) { 157 | return $generated_args; 158 | } 159 | 160 | $created = $this->_createObject( $generated_args ); 161 | if ( ! $created || is_wp_error( $created ) ) { 162 | return $created; 163 | } 164 | 165 | if ( ! empty( $callbacks ) ) { 166 | $updated_fields = $this->_applyCallbacks( $callbacks, $created ); 167 | $save_result = $this->_updateObject( $created, $updated_fields ); 168 | if ( ! $save_result || is_wp_error( $save_result ) ) { 169 | return $save_result; 170 | } 171 | } 172 | 173 | if ( ! empty( $created ) && !is_wp_error( $created ) ) { 174 | $this->_objects[] = $created; 175 | } 176 | 177 | return $created; 178 | } 179 | 180 | /** 181 | * Creates a new object and returns it. 182 | * 183 | * @since 1.0.0 184 | * 185 | * @access public 186 | * @param array $args The array of arguments to use during a new object creation. 187 | * @param array $definitions Custom difinitions of default values for a new object properties. 188 | * @return object|boolean|\WP_Error A new object on success, otherwise FALSE or a WP_Error object. 189 | */ 190 | public function createAndGet( $args = array(), $definitions = null ) { 191 | $object_id = $this->create( $args, $definitions ); 192 | if ( ! $object_id || is_wp_error( $object_id ) ) { 193 | return $object_id; 194 | } 195 | 196 | return $this->getObjectById( $object_id ); 197 | } 198 | 199 | /** 200 | * Creates many new objects and returns their ids. 201 | * 202 | * @since 1.0.0 203 | * 204 | * @access public 205 | * @param int $count The number of objects to created. 206 | * @param array $args The array of arguments to use during a new object creation. 207 | * @param array $definitions Custom difinitions of default values for a new object properties. 208 | * @return array The array of generated object ids. 209 | */ 210 | public function createMany( $count, $args = array(), $definitions = null ) { 211 | $results = array(); 212 | for ( $i = 0; $i < $count; $i++ ) { 213 | $results[] = $this->create( $args, $definitions ); 214 | } 215 | 216 | return $results; 217 | } 218 | 219 | /** 220 | * Deletes an object generated by this factory instance. 221 | * 222 | * @since 1.0.0 223 | * 224 | * @access public 225 | * @param mixed $object The generated object. 226 | * @return boolean|\WP_Error TRUE on success, otherwise FALSE or WP_Error object. 227 | */ 228 | public function delete( $object ) { 229 | // do nothing if an object was generated not by this factory 230 | if ( ! in_array( $object, $this->_objects ) ) { 231 | return false; 232 | } 233 | 234 | // delete object and remove it from the objects list 235 | $deleted = $this->_deleteObject( $object ); 236 | if ( $deleted && ! is_wp_error( $deleted ) ) { 237 | $index = array_search( $object, $this->_objects ); 238 | if ( false !== $index ) { 239 | unset( $this->_objects[ $index ] ); 240 | } 241 | } 242 | 243 | return $deleted; 244 | } 245 | 246 | /** 247 | * Deletes all objects generated by this factory instance. 248 | * 249 | * @since 1.0.0 250 | * 251 | * @access public 252 | */ 253 | public function deleteAll() { 254 | foreach ( $this->_objects as $object ) { 255 | $this->delete( $object ); 256 | } 257 | } 258 | 259 | /** 260 | * Generates arguments for a new object. 261 | * 262 | * @since 1.0.0 263 | * 264 | * @access protected 265 | * @param array $args The initial set of arguments. 266 | * @param array $definitions The definitions for auto generated properties. 267 | * @param array $callbacks The array of callbacks. 268 | * @return array|\WP_Error The array of arguments on success, otherwise a WP_Error object. 269 | */ 270 | protected function _generateArgs( $args, $definitions, &$callbacks = array() ) { 271 | foreach ( $definitions as $field => $generator ) { 272 | if ( isset( $args[ $field ] ) ) { 273 | continue; 274 | } 275 | 276 | if ( is_scalar( $generator ) ) { 277 | $args[ $field ] = $generator; 278 | } elseif ( $generator instanceof AfterCreateCallback ) { 279 | $callbacks[ $field ] = $generator; 280 | } elseif ( $generator instanceof Sequence ) { 281 | $args[ $field ] = $generator->next(); 282 | } else { 283 | return new \WP_Error( 'invalid_argument', 'Factory default value should be either a scalar or an generator object.' ); 284 | } 285 | } 286 | 287 | return $args; 288 | } 289 | 290 | /** 291 | * Applies callbacks and returns updated fields. 292 | * 293 | * @since 1.0.0 294 | * 295 | * @access protected 296 | * @param array $callbacks The array of callbacks to call. 297 | * @param mixed $created The newly created object. 298 | * @return array The array of updated fields. 299 | */ 300 | protected function _applyCallbacks( $callbacks, $created ) { 301 | $updated_fields = array(); 302 | foreach ( $callbacks as $field => $callback ) { 303 | $updated_fields[ $field ] = $callback->call( $created ); 304 | } 305 | 306 | return $updated_fields; 307 | } 308 | 309 | /** 310 | * Returns callback wrapper which will be called after a new object created. 311 | * 312 | * @since 1.0.0 313 | * 314 | * @access public 315 | * @param callable $function The callback to call after a new object created. 316 | * @return \WPCC\Component\Factory\Callback\AfterCreate 317 | */ 318 | public function callback( $function ) { 319 | return new AfterCreateCallback( $function ); 320 | } 321 | 322 | /** 323 | * Adds slashes recursively to each item of incomming value. 324 | * 325 | * @since 1.0.0 326 | * 327 | * @access protected 328 | * @param mixed $value The incomming value to add slashes to. 329 | * @return mixed Updated value with slashes. 330 | */ 331 | protected function _addSlashesDeep( $value ) { 332 | if ( is_array( $value ) ) { 333 | $value = array_map( array( $this, '_addSlashesDeep' ), $value ); 334 | } elseif ( is_object( $value ) ) { 335 | $vars = get_object_vars( $value ); 336 | foreach ( $vars as $key => $data ) { 337 | $value->{$key} = $this->_addSlashesDeep( $data ); 338 | } 339 | } elseif ( is_string( $value ) ) { 340 | $value = addslashes( $value ); 341 | } 342 | 343 | return $value; 344 | } 345 | 346 | } -------------------------------------------------------------------------------- /classes/WPCC/CLI/Codeception.php: -------------------------------------------------------------------------------- 1 | 46 | * : The suite name to run. There are three types of suites available to 47 | * use: unit, functional and acceptance, but currently only acceptance tests 48 | * are supported. 49 | * 50 | * 51 | * : The test name to run. 52 | * 53 | * 54 | * : Path to the custom config file. 55 | * 56 | * 57 | * : Determines whether to show output in compact style or not. 58 | * 59 | * 60 | * : Tells to generate html with results (default: "report.html"). 61 | * 62 | * 63 | * : Tells to generate JUnit XML Log (default: "report.xml"). 64 | * 65 | * 66 | * : Tells to generate Tap Log (default: "report.tap.log"). 67 | * 68 | * 69 | * : Tells to generate Json Log (default: "report.json"). 70 | * 71 | * 72 | * : Tells to use colors in output. 73 | * 74 | * 75 | * : Forces no colors in output (useful to override config file). 76 | * 77 | * 78 | * : Tells to output only suite names and final results. 79 | * 80 | * 81 | * : Determines whether to show test steps in output or not. 82 | * 83 | * 84 | * : Determines whether to show debug and scenario output or not. 85 | * 86 | * 87 | * : Determines whether to run with code coverage (default: "coverage.serialized") or not. 88 | * 89 | * 90 | * : Tells to generate CodeCoverage HTML report in path (default: "coverage"). 91 | * 92 | * 93 | * : Tells to generate CodeCoverage XML report in file (default: "coverage.xml"). 94 | * 95 | * 96 | * : Tells to generate CodeCoverage text report in file (default: "coverage.txt"). 97 | * 98 | * 99 | * : Tells to not finish with exit code. 100 | * 101 | * 102 | * : Environment to use during tests execution. 103 | * 104 | * 105 | * : Tells to stop after first failure. 106 | * 107 | * 108 | * : Tells to not output any message. 109 | * 110 | * 111 | * : Forces ANSI output. 112 | * 113 | * 114 | * : Tells to disable ANSI output. 115 | * 116 | * 117 | * : Tells to not ask any interactive question. 118 | * 119 | * ### EXAMPLE 120 | * 121 | * wp codeception run 122 | * wp codeception run acceptance --steps --html 123 | * wp codeception run acceptance MyAwesomeTest --debug 124 | * wp codeception run functional --env=staging 125 | * 126 | * @synopsis [] [] [--config=] [--report] [--html=] [--xml=] [--tap=] [--json=] [--colors] [--no-colors] [--silent] [--steps] [--debug] [--coverage] [--coverage-html] [--coverage-xml] [--coverage-text] [--no-exit] [--env=] [--fail-fast] [--quiet] [--ansi] [--no-ansi] [--no-interaction] 127 | * 128 | * @since 1.0.0 129 | * 130 | * @todo Implement all arguments for run command (http://codeception.com/docs/reference/Commands). 131 | * 132 | * @access public 133 | * @param array $args Unassociated array of arguments passed to this command. 134 | * @param array $assoc_args Associated array of arguments passed to this command. 135 | */ 136 | public function run( $args, $assoc_args ) { 137 | $app = new Application( 'Codeception', \Codeception\Codecept::VERSION ); 138 | $app->add( new \WPCC\Command\Run( 'run' ) ); 139 | $app->run( new ArgvInput() ); 140 | } 141 | 142 | /** 143 | * Executes command. 144 | * 145 | * @since 1.0.0 146 | * 147 | * @access private 148 | */ 149 | private function _execute_command() { 150 | $app = new Application( 'Codeception', \Codeception\Codecept::VERSION ); 151 | $app->add( new \WPCC\Command\Build( 'build' ) ); 152 | $app->add( new \Codeception\Command\Console( 'console' ) ); 153 | $app->add( new \WPCC\Command\Bootstrap( 'bootstrap' ) ); 154 | $app->add( new \Codeception\Command\GenerateCept( 'generate-cept' ) ); 155 | $app->add( new \Codeception\Command\GenerateCest( 'generate-cest' ) ); 156 | $app->add( new \Codeception\Command\GenerateTest( 'generate-test' ) ); 157 | $app->add( new \Codeception\Command\GeneratePhpUnit( 'generate-phpunit' ) ); 158 | $app->add( new \Codeception\Command\GenerateSuite( 'generate-suite' ) ); 159 | $app->add( new \Codeception\Command\GenerateHelper( 'generate-helper' ) ); 160 | $app->add( new \Codeception\Command\GenerateScenarios( 'generate-scenarios' ) ); 161 | $app->add( new \Codeception\Command\Clean( 'clean' ) ); 162 | $app->add( new \Codeception\Command\GenerateGroup( 'generate-group' ) ); 163 | $app->add( new \Codeception\Command\GeneratePageObject( 'generate-pageobject' ) ); 164 | $app->add( new \Codeception\Command\GenerateStepObject( 'generate-stepobject' ) ); 165 | $app->run( new ArgvInput() ); 166 | } 167 | 168 | /** 169 | * Creates default config, tests directory and sample suites for current 170 | * project. Use this command to start building a test suite. 171 | * 172 | * By default it will create 3 suites acceptance, functional, and unit. To 173 | * customize run this command with --customize option. 174 | * 175 | * ### OPTIONS 176 | * 177 | * 178 | * : Sets manually actors and suite names during setup. 179 | * 180 | * 181 | * : Creates tests with provided namespace for actor classes and helpers. 182 | * 183 | * 184 | * : Sets actor name to create {SUITE}{NAME} actor class. 185 | * 186 | * 187 | * : Sets path to a project, where tests should be placed. 188 | * 189 | * ### EXAMPLE 190 | * 191 | * wp codeception bootstrap 192 | * wp codeception bootstrap --customize 193 | * wp codeception bootstrap --namespace="Frontend\Tests" 194 | * wp codeception bootstrap --actor=Tester 195 | * wp codeception bootstrap path/to/the/project --customize 196 | * 197 | * @synopsis [] [--customize] [--namespace=] [--actor=] 198 | * 199 | * @since 1.0.0 200 | * 201 | * @access public 202 | * @param array $args Unassociated arguments passed to the command. 203 | * @param array $assoc_args Associated arguments passed to the command. 204 | */ 205 | public function bootstrap( $args, $assoc_args ) { 206 | $this->_execute_command(); 207 | } 208 | 209 | /** 210 | * Creates a new group class. 211 | * 212 | * ### OPTIONS 213 | * 214 | * 215 | * : The group class name to create. 216 | * 217 | * 218 | * : Path to the custom config file. 219 | * 220 | * ### EXAMPLE 221 | * 222 | * wp codeception generate-group Admin 223 | * wp codeception generate-group Admin --config=/path/to/config.yml 224 | * 225 | * @subcommand generate-group 226 | * @synopsis [--config=] 227 | * 228 | * @since 1.0.0 229 | * 230 | * @access public 231 | * @param array $args Unassociated arguments passed to the command. 232 | * @param array $assoc_args Associated arguments passed to the command. 233 | */ 234 | public function generate_group( $args, $assoc_args ) { 235 | $this->_execute_command(); 236 | } 237 | 238 | /** 239 | * Creates a new test suite. 240 | * 241 | * ### OPTIONS 242 | * 243 | * 244 | * : The suite name to create. 245 | * 246 | * 247 | * : The actor name for the suite. 248 | * 249 | * 250 | * : Path to the custom config file. 251 | * 252 | * ### EXAMPLE 253 | * 254 | * wp codeception generate-suite api 255 | * wp codeception generate-suite integration Code 256 | * wp codeception generate-suite frontend Front 257 | * 258 | * @subcommand generate-suite 259 | * @synopsis [] [--config=] 260 | * 261 | * @since 1.0.0 262 | * 263 | * @access public 264 | * @param array $args Unassociated arguments passed to the command. 265 | * @param array $assoc_args Associated arguments passed to the command. 266 | */ 267 | public function generate_suite( $args, $assoc_args ) { 268 | $this->_execute_command(); 269 | } 270 | 271 | /** 272 | * Creates a new Cept (scenario-driven test) file. 273 | * 274 | * ### OPTIONS 275 | * 276 | * 277 | * : The suite name where to add a new Cept. 278 | * 279 | * 280 | * : The name for a new Cept file. 281 | * 282 | * 283 | * : Path to the custom config file. 284 | * 285 | * ### EXAMPLE 286 | * 287 | * wp codeception generate-cept suite Login 288 | * wp codeception generate-cept suite Front 289 | * wp codeception generate-cept suite subdir/subdir/testnameCept.php 290 | * 291 | * @subcommand generate-cept 292 | * @synopsis [--config=] 293 | * 294 | * @since 1.0.0 295 | * 296 | * @access public 297 | * @param array $args Unassociated arguments passed to the command. 298 | * @param array $assoc_args Associated arguments passed to the command. 299 | */ 300 | public function generate_cept( $args, $assoc_args ) { 301 | $this->_execute_command(); 302 | } 303 | 304 | /** 305 | * Creates a new Cest (scenario-driven object-oriented test) file. 306 | * 307 | * ### OPTIONS 308 | * 309 | * 310 | * : The suite name where to add a new Cest. 311 | * 312 | * 313 | * : The name for a new Cest class. 314 | * 315 | * 316 | * : Path to the custom config file. 317 | * 318 | * ### EXAMPLE 319 | * 320 | * wp codeception generate-cest suite Login 321 | * wp codeception generate-cest suite subdir/subdir/testnameCest.php 322 | * wp codeception generate-cest suite "App\Login" 323 | * 324 | * @subcommand generate-cest 325 | * @synopsis [--config=] 326 | * 327 | * @since 1.0.0 328 | * 329 | * @access public 330 | * @param array $args Unassociated arguments passed to the command. 331 | * @param array $assoc_args Associated arguments passed to the command. 332 | */ 333 | public function generate_cest( $args, $assoc_args ) { 334 | $this->_execute_command(); 335 | } 336 | 337 | /** 338 | * Creates a skeleton for Unit Test that extends \Codeception\TestCase\Test class. 339 | * 340 | * ### OPTIONS 341 | * 342 | * 343 | * : The suite name where to add a new test. 344 | * 345 | * 346 | * : The name for a new test class. 347 | * 348 | * 349 | * : Path to the custom config file. 350 | * 351 | * ### EXAMPLE 352 | * 353 | * wp codeception generate-test unit User 354 | * wp codeception generate-test unit "App\User" 355 | * 356 | * @subcommand generate-test 357 | * @synopsis [--config=] 358 | * 359 | * @since 1.0.0 360 | * 361 | * @access public 362 | * @param array $args Unassociated arguments passed to the command. 363 | * @param array $assoc_args Associated arguments passed to the command. 364 | */ 365 | public function generate_test( $args, $assoc_args ) { 366 | $this->_execute_command(); 367 | } 368 | 369 | /** 370 | * Creates a skeleton for unit test as in classical PHPUnit. 371 | * 372 | * ### OPTIONS 373 | * 374 | * 375 | * : The suite name where to add a new test. 376 | * 377 | * 378 | * : The name for a new test class. 379 | * 380 | * 381 | * : Path to the custom config file. 382 | * 383 | * ### EXAMPLE 384 | * 385 | * wp codeception generate-phpunit unit User 386 | * wp codeception generate-phpunit unit "App\User" 387 | * 388 | * @subcommand generate-phpunit 389 | * @synopsis [--config=] 390 | * 391 | * @since 1.0.0 392 | * 393 | * @access public 394 | * @param array $args Unassociated arguments passed to the command. 395 | * @param array $assoc_args Associated arguments passed to the command. 396 | */ 397 | public function generate_phpunit( $args, $assoc_args ) { 398 | $this->_execute_command(); 399 | } 400 | 401 | /** 402 | * Creates an empty Helper class. 403 | * 404 | * ### OPTIONS 405 | * 406 | * 407 | * : The hlper name to create. 408 | * 409 | * 410 | * : Path to the custom config file. 411 | * 412 | * ### EXAMPLE 413 | * 414 | * wp codeception generate-helper MyHelper 415 | * 416 | * @subcommand generate-helper 417 | * @synopsis [--config=] 418 | * 419 | * @since 1.0.0 420 | * 421 | * @access public 422 | * @param array $args Unassociated arguments passed to the command. 423 | * @param array $assoc_args Associated arguments passed to the command. 424 | */ 425 | public function generate_helper( $args, $assoc_args ) { 426 | $this->_execute_command(); 427 | } 428 | 429 | /** 430 | * Generates user-friendly text scenarios from scenario-driven tests (Cest, Cept). 431 | * 432 | * ### OPTIONS 433 | * 434 | * 435 | * : The suite name to create scenarios for. 436 | * 437 | * 438 | * : Path to the custom config file. 439 | * 440 | * 441 | * : The specified path as destination instead of default. 442 | * 443 | * 444 | * : Specifies output format: html or text (default). 445 | * 446 | * 447 | * : Indicates to render all scenarios to only one file. 448 | * 449 | * ### EXAMPLE 450 | * 451 | * wp codeception generate-scenarios acceptance 452 | * wp codeception generate-scenarios acceptance --format html 453 | * wp codeception generate-scenarios acceptance --path doc 454 | * 455 | * @subcommand generate-scenarios 456 | * @synopsis [--config=] [--path=] [--format=] [--single-file] 457 | * 458 | * @since 1.0.0 459 | * 460 | * @access public 461 | * @param array $args Unassociated arguments passed to the command. 462 | * @param array $assoc_args Associated arguments passed to the command. 463 | */ 464 | public function generate_scenarios( $args, $assoc_args ) { 465 | $this->_execute_command(); 466 | } 467 | 468 | /** 469 | * Creates a new PageObject class. 470 | * 471 | * ### OPTIONS 472 | * 473 | * 474 | * : The suite name where to add a new page object. 475 | * 476 | * 477 | * : The page object name to create. 478 | * 479 | * 480 | * : Path to the custom config file. 481 | * 482 | * ### EXAMPLE 483 | * 484 | * wp codeception generate-pageobject acceptance Login 485 | * 486 | * @subcommand generate-pageobject 487 | * @synopsis [--config=] 488 | * 489 | * @since 1.0.0 490 | * 491 | * @access public 492 | * @param array $args Unassociated arguments passed to the command. 493 | * @param array $assoc_args Associated arguments passed to the command. 494 | */ 495 | public function generate_pageobject( $args, $assoc_args ) { 496 | $this->_execute_command(); 497 | } 498 | 499 | /** 500 | * Creates a new StepObject class. 501 | * 502 | * ### OPTIONS 503 | * 504 | * 505 | * : The suite name where to add a new step object. 506 | * 507 | * 508 | * : The step object name to create. 509 | * 510 | * 511 | * : Path to the custom config file. 512 | * 513 | * 514 | * : Determines whether or not to skip verification questions. 515 | * 516 | * ### EXAMPLE 517 | * 518 | * wp codeception generate-stepobject acceptance AdminSteps 519 | * 520 | * @subcommand generate-stepobject 521 | * @synopsis [--config=] [--silent] 522 | * 523 | * @since 1.0.0 524 | * 525 | * @access public 526 | * @param array $args Unassociated arguments passed to the command. 527 | * @param array $assoc_args Associated arguments passed to the command. 528 | */ 529 | public function generate_stepobject( $args, $assoc_args ) { 530 | $this->_execute_command(); 531 | } 532 | 533 | /** 534 | * Cleans output directory. 535 | * 536 | * ### OPTIONS 537 | * 538 | * 539 | * : Path to the custom config file. 540 | * 541 | * ### EXAMPLE 542 | * 543 | * wp codeception clean --config=/path/to/the/config.yml 544 | * 545 | * @synopsis [--config=] 546 | * 547 | * @since 1.0.0 548 | * 549 | * @access public 550 | * @param array $args Unassociated arguments passed to the command. 551 | * @param array $assoc_args Associated arguments passed to the command. 552 | */ 553 | public function clean( $args, $assoc_args ) { 554 | $this->_execute_command(); 555 | } 556 | 557 | /** 558 | * Generates Actor classes from suite configs. Currently actor classes are 559 | * auto-generated. Use this command to generate them manually. 560 | * 561 | * ### OPTIONS 562 | * 563 | * 564 | * : Path to the custom config file. 565 | * 566 | * ### EXAMPLE 567 | * 568 | * wp codeception build --config=/path/to/the/config.yml 569 | * 570 | * @synopsis [--config=] 571 | * 572 | * @since 1.0.0 573 | * 574 | * @access public 575 | * @param array $args Unassociated arguments passed to the command. 576 | * @param array $assoc_args Associated arguments passed to the command. 577 | */ 578 | public function build( $args, $assoc_args ) { 579 | $this->_execute_command(); 580 | } 581 | 582 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------