diff --git a/.github/workflows/product-creation-tests.yml b/.github/workflows/product-creation-tests.yml index b6a4f0543..159cfe6f3 100644 --- a/.github/workflows/product-creation-tests.yml +++ b/.github/workflows/product-creation-tests.yml @@ -9,6 +9,13 @@ on: required: false type: boolean default: false + workflow_dispatch: + inputs: + use-marketplace-version: + description: 'Should this use marketplace version of the plugin' + required: false + type: boolean + default: false pull_request: branches: [ main, master, develop ] @@ -149,7 +156,18 @@ jobs: cd /tmp/wordpress fi - # Activate the plugin + # Configure Facebook connection before activation + wp option update wc_facebook_access_token "${{ secrets.FB_ACCESS_TOKEN }}" --allow-root + wp option update wc_facebook_merchant_access_token "${{ secrets.FB_ACCESS_TOKEN }}" --allow-root + wp option update wc_facebook_business_manager_id "${{ secrets.FB_BUSINESS_MANAGER_ID }}" --allow-root + wp option update wc_facebook_external_business_id "${{ secrets.FB_EXTERNAL_BUSINESS_ID }}" --allow-root + wp option update wc_facebook_product_catalog_id "${{ secrets.FB_PRODUCT_CATALOG_ID }}" --allow-root + wp option update wc_facebook_pixel_id "${{ secrets.FB_PIXEL_ID }}" --allow-root + wp option update wc_facebook_has_connected_fbe_2 "yes" --allow-root + wp option update wc_facebook_has_authorized_pages_read_engagement "yes" --allow-root + wp option update wc_facebook_enable_product_sync "yes" --allow-root + wp option update wc_facebook_page_id "${{ secrets.FB_PAGE_ID }}" --allow-root + # Activate the plugin (this triggers automatic sync) wp plugin activate facebook-for-woocommerce --allow-root @@ -166,6 +184,21 @@ jobs: # Test if site is accessible curl -f http://localhost:8080 || exit 1 + - name: Verify Facebook for WooCommerce setup + run: | + cd /tmp/wordpress + echo "=== Facebook for WooCommerce Info ===" + wp eval " + if (function_exists('facebook_for_woocommerce')) { + \$connection = facebook_for_woocommerce()->get_connection_handler(); + echo 'Connected: ' . (\$connection->is_connected() ? 'YES' : 'NO') . PHP_EOL; + echo 'Access Token: ' . (\$connection->get_access_token() ? 'Present' : 'Missing') . PHP_EOL; + echo 'External Business ID: ' . \$connection->get_external_business_id() . PHP_EOL; + echo 'Business Manager ID: ' . \$connection->get_business_manager_id() . PHP_EOL; + } else { + echo 'Facebook plugin not loaded properly'; + }" --allow-root + - name: Install Playwright run: | npm install diff --git a/includes/API.php b/includes/API.php index 78693d2a7..5a03cf7e4 100644 --- a/includes/API.php +++ b/includes/API.php @@ -463,6 +463,23 @@ public function get_product_facebook_ids( string $facebook_product_catalog_id, s } + /** + * Returns requested fields from Facebook for the given product. + * + * @param string $facebook_product_catalog_id + * @param string $facebook_retailer_id + * @param string $fields_string Comma-separated string of fields to request from Facebook API. + * @return API\Response|API\ProductCatalog\Products\Id\Response + * @throws ApiException In case of network request error. + * @throws API\Exceptions\Request_Limit_Reached In case of rate limit error. + */ + public function get_product_facebook_fields( string $facebook_product_catalog_id, string $facebook_retailer_id, string $fields_string = 'id,product_group{id}' ): API\ProductCatalog\Products\Id\Response { + $request = new API\ProductCatalog\Products\Id\Request( $facebook_product_catalog_id, $facebook_retailer_id, $fields_string ); + $this->set_response_handler( API\ProductCatalog\Products\Id\Response::class ); + return $this->perform_request( $request ); + } + + /** * @param string $product_catalog_id * @param array $data diff --git a/includes/API/ProductCatalog/Products/Id/Request.php b/includes/API/ProductCatalog/Products/Id/Request.php index af39f78de..d0cfcfbd2 100644 --- a/includes/API/ProductCatalog/Products/Id/Request.php +++ b/includes/API/ProductCatalog/Products/Id/Request.php @@ -17,8 +17,9 @@ class Request extends ApiRequest { /** * @param string $facebook_product_catalog_id Facebook Product Catalog ID. * @param string $facebook_product_retailer_id Facebook Product Retailer ID. + * @param string $fields_string Comma-separated string of fields to request from Facebook API. */ - public function __construct( string $facebook_product_catalog_id, string $facebook_product_retailer_id ) { + public function __construct( string $facebook_product_catalog_id, string $facebook_product_retailer_id, string $fields_string = 'id,product_group{id}' ) { /** * We use the endpoint with filter to get the product id and group id for new products to check if the product is already synced to Facebook. @@ -29,7 +30,7 @@ public function __construct( string $facebook_product_catalog_id, string $facebo $this->set_params( array( 'filter' => '{"retailer_id":{"eq":"' . $facebook_product_retailer_id . '"}}', - 'fields' => 'id,product_group{id}', + 'fields' => $fields_string, ) ); } diff --git a/tests/e2e/e2e-facebook-sync-validator.php b/tests/e2e/e2e-facebook-sync-validator.php new file mode 100644 index 000000000..31dc68548 --- /dev/null +++ b/tests/e2e/e2e-facebook-sync-validator.php @@ -0,0 +1,503 @@ + checkSync -> compareFields + * + * Usage: php e2e-facebook-sync-validator.php [wait_seconds] + */ + +// Bootstrap WordPress +$wp_path = '/tmp/wordpress/wp-load.php'; + +if (!file_exists($wp_path)) { + echo json_encode([ + 'success' => false, + 'error' => 'WordPress not found at: ' . $wp_path + ]); + exit(1); +} + +require_once($wp_path); + +/** + * Facebook Sync Validator Class + */ +class FacebookSyncValidator { + + private $product_id; + private $product; + private $integration; + private $result; + + /** + * Field mappings between WooCommerce and Facebook fields + */ + private const FIELD_MAPPINGS = [ + 'title' => 'name', + 'price' => 'price', + 'retailer_id' => 'retailer_id', + 'availability' => 'availability', + 'description' => 'description', + 'brand' => 'brand', + 'condition' => 'condition' + ]; + + /** + * Helper method to add debug messages + */ + private function debug($message) { + $this->result['debug'][] = $message; + } + + + /** + * Initialize the validator and verify dependencies + */ + public function __construct($product_id, $wait_seconds = 5) { + $this->product_id = (int)$product_id; + $this->result = [ + 'success' => false, + 'product_id' => $this->product_id, + 'product_type' => 'unknown', + 'sync_status' => 'unknown', + 'retailer_id' => null, + 'facebook_id' => null, + 'mismatches' => [], + 'summary' => [], + 'debug' => [], + 'error' => null + ]; + + // Wait for Facebook processing + if ($wait_seconds > 0) { + sleep($wait_seconds); + $this->debug("Waited {$wait_seconds} seconds before validation"); + } + + $this->validateDependencies(); + $this->initializeProduct(); + $this->initializeIntegration(); + } + + /** + * Check if required plugins and extensions are available + */ + private function validateDependencies() { + if (!function_exists('wc_get_product')) { + throw new Exception('WooCommerce not active'); + } + if (!function_exists('facebook_for_woocommerce')) { + throw new Exception('Facebook plugin not loaded'); + } + if (!$this->product_id) { + throw new Exception('Product ID required'); + } + } + + /** + * Initialize product + */ + private function initializeProduct() { + $this->debug("Initializing product: {$this->product_id}"); + $this->product = wc_get_product($this->product_id); + + // Fail fast if the product ID doesn't exist in WooCommerce + if (!$this->product) { + throw new Exception("Product {$this->product_id} not found"); + } + + // Get and log retailer ID + $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id($this->product); + $this->debug("Product retailer ID: {$retailer_id} and type: {$this->product->get_type()}"); + + $this->result['product_type'] = $this->product->get_type(); + $this->debug("Initialized {$this->result['product_type']} product: {$this->product->get_name()}"); + } + + /** + * Set up Facebook API integration and verify configuration + */ + private function initializeIntegration() { + $this->integration = facebook_for_woocommerce()->get_integration(); + if (!$this->integration) { + throw new Exception('Facebook integration not available'); + } + if (!$this->integration->is_configured()) { + throw new Exception('Facebook integration not configured'); + } + $this->debug('Facebook integration initialized and configured'); + } + + /** + * Main validation method - validates sync between WooCommerce and Facebook + * 1. Get both platform data (WooCommerce + Facebook) + * 2. Check sync status using fetched Facebook data + * 3. Compare fields between platforms + * 4. Set success based on sync status and no mismatches + */ + public function validate() { + try { + $actual_type = $this->product->get_type(); + + // Step 1: Get both platform data (WooCommerce + Facebook) + $data = $this->getBothPlatformData($actual_type); + + // Step 2: Check sync status using fetched Facebook data + $this->checkSyncStatus($data); + + // Step 3: Compare fields between platforms + $this->compareFields($data); + + // Set success based on sync status and no mismatches + $this->result['success'] = ($this->result['sync_status'] === 'synced' && count($this->result['mismatches']) === 0); + } catch (Exception $e) { + $this->result['error'] = $e->getMessage(); + $this->debug("Validation failed: " . $e->getMessage()); + } + return $this->result; + } + + /** + * Get both WooCommerce and Facebook data for any product type + */ + private function getBothPlatformData($product_type) { + $this->debug("Fetching both platform data for {$product_type} product"); + + if ($product_type === 'variable') { + return $this->getVariableProductData(); + } else { + return $this->getSimpleProductData(); + } + } + + /** + * Get data for simple products + */ + private function getSimpleProductData() { + // Get WooCommerce data + $retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id($this->product); + $this->result['retailer_id'] = $retailer_id; + + $woo_data = $this->extractWooCommerceFields($this->product, $retailer_id); + $this->debug("Extracted WooCommerce data for simple product"); + // $this->debug("WooCommerce data: " . json_encode($woo_data, JSON_PRETTY_PRINT)); + + // Get Facebook data + $facebook_data = $this->fetchFacebookData($retailer_id, 'simple'); + + return [ + 'type' => 'simple', + 'woo_data' => [$woo_data], + 'facebook_data' => [$facebook_data] + ]; + } + + /** + * Get data for variable products (variations only) + */ + private function getVariableProductData() { + $failed_variations = []; + $woo_data_array = []; + $facebook_data_array = []; + + // Set parent retailer_id for result tracking + $this->result['retailer_id'] = WC_Facebookcommerce_Utils::get_fb_retailer_id($this->product); + + // All variations + $variations = $this->product->get_children(); + $this->debug("Processing " . count($variations) . " variations: [" . implode(', ', $variations) . "]"); + + foreach ($variations as $variation_id) { + $variation = wc_get_product($variation_id); + + try { + $var_retailer_id = WC_Facebookcommerce_Utils::get_fb_retailer_id($variation); + $woo_data_array[] = $this->extractWooCommerceFields($variation, $var_retailer_id); + $facebook_data_array[] = $this->fetchFacebookData($var_retailer_id, 'variable'); + + $this->debug("Extracted variation {$variation_id} data successfully"); + } catch (Exception $e) { + $failed_variations[] = $variation_id; + $this->debug("Variation {$variation_id} data extraction failed: " . $e->getMessage()); + } + } + + // Summary for variable products + $total_variations = count($variations); + $successful_variations = $total_variations - count($failed_variations); + $this->result['summary'] = [ + 'total_variations' => $total_variations, + 'successful_variations' => $successful_variations, + 'failed_variations' => count($failed_variations), + 'failed_variation_ids' => $failed_variations + ]; + + if (count($failed_variations) > 0) { + $this->debug("Failed to process variations: " . implode(', ', $failed_variations)); + } + + return [ + 'type' => 'variable', + 'woo_data' => $woo_data_array, + 'facebook_data' => $facebook_data_array + ]; + } + + /** + * Extract WooCommerce product fields + */ + private function extractWooCommerceFields($product, $retailer_id) { + // Create Facebook product wrapper to get the prepared data; variations will have parent product + $fb_product = $product->get_parent_id() ? + new WC_Facebook_Product($product, new WC_Facebook_Product(wc_get_product($product->get_parent_id()))) : + new WC_Facebook_Product($product); + + $product_data = $fb_product->prepare_product($retailer_id, WC_Facebook_Product::PRODUCT_PREP_TYPE_ITEMS_BATCH); + return [ + 'id' => $product->get_id(), // Always include id for both simple and variable products + 'title' => $product_data['title'] ?? $product->get_name(), + 'price' => $product_data['price'] ?? $product->get_regular_price(), + 'description' => $this->truncateText($product_data['description'] ?? '', 100), + 'availability' => $product_data['availability'] ?? '', + 'retailer_id' => $retailer_id, + 'condition' => $product_data['condition'] ?? '', + 'brand' => $product_data['brand'] ?? '', + 'color' => $product_data['color'] ?? '', + 'size' => $product_data['size'] ?? '' + ]; + } + + /** + * Fetch Facebook data via API + */ + private function fetchFacebookData($retailer_id, $context = 'simple') { + try { + $api = facebook_for_woocommerce()->get_api(); + $catalog_id = $this->integration->get_product_catalog_id(); + + // Use get_product_facebook_fields with full fields string + $fields = 'id,name,price,description,availability,retailer_id,condition,brand,color,size,product_group{id}'; + $response = $api->get_product_facebook_fields($catalog_id, $retailer_id, $fields); + + // Log the full API response for debugging + // $this->debug("Facebook API response for {$retailer_id}: " . json_encode($response, JSON_PRETTY_PRINT)); + + if ($response && $response->response_data && isset($response->response_data['data'][0])) { + $fb_data = $response->response_data['data'][0]; + $this->debug("Successfully fetched Facebook data for {$retailer_id}"); + + return [ + 'id' => $fb_data['id'] ?? null, + 'name' => $fb_data['name'] ?? '', + 'price' => $fb_data['price'] ?? '', + 'description' => $fb_data['description'] ?? '', + 'availability' => $fb_data['availability'] ?? '', + 'retailer_id' => $fb_data['retailer_id'] ?? '', + 'condition' => $fb_data['condition'] ?? '', + 'brand' => $fb_data['brand'] ?? '', + 'color' => $fb_data['color'] ?? '', + 'size' => $fb_data['size'] ?? '', + 'product_group_id' => $fb_data['product_group']['id'] , //Simple products also have some product_group id + 'found' => true + ]; + + } else { + $this->debug("No Facebook data found for retailer_id: {$retailer_id}"); + return ['found' => false]; + } + + } catch (Exception $e) { + $this->debug("Facebook API error for {$retailer_id}: " . $e->getMessage()); + return ['found' => false, 'error' => $e->getMessage()]; + } + } + + /** + * Check if products are synced to Facebook (unified for both simple and variable) + */ + private function checkSyncStatus($data) { + $total_product_count = count($data['woo_data']); + $synced_products = array_filter($data['facebook_data'], function($fb_data) { + return $fb_data['found'] ?? false; + }); + $synced_count = count($synced_products); + + // Get unique product group IDs from synced products + $product_group_ids = array_unique(array_filter(array_map(function($fb_data) { + return $fb_data['product_group_id'] ?? null; + }, $synced_products))); + + // Synced if: + // 1. ALL products/variations exist in Facebook + // 2. All products/variations belong to the same product group + if ($total_product_count > 0 && $synced_count === $total_product_count && count($product_group_ids) === 1) { + $this->result['sync_status'] = 'synced'; + $this->result['facebook_id'] = reset($product_group_ids); // Use the common group ID + $this->debug("{$data['type']} Product {$this->result['retailer_id']} is fully synced with Facebook product group: {$this->result['facebook_id']}"); + + } else { + $this->result['sync_status'] = 'not_synced'; + + if ($synced_count < $total_product_count) { + // Find missing products/variations + $missing_items = []; + for ($i = 0; $i < $total_product_count; $i++) { + if (!($data['facebook_data'][$i]['found'] ?? false)) { + // cos we can't just loop on $data['facebook_data'] as it does not have retailer_id during failure fetches + $product_id = $data['woo_data'][$i]['id'] ?? "unknown_{$i}"; + $retailer_id = $data['woo_data'][$i]['retailer_id'] ?? "unknown_retailer_{$i}"; + $missing_items[] = "ID:{$product_id} (retailer:{$retailer_id})"; + } + } + + $this->debug("Products/variations not synced to Facebook: " . implode(', ', $missing_items)); + + } elseif (count($product_group_ids) > 1) { + $product_type = $data['type'] === 'variable' ? 'variations' : 'product'; + $this->debug("{$data['type']} product not synced - {$product_type} belong to different product groups: " . implode(', ', $product_group_ids)); + } + } + } + + /** + * Compare fields between WooCommerce and Facebook for all products + */ + private function compareFields($data) { + $mismatches = []; + $compared_products = 0; + + // Loop through each product (simple = 1 item, variable = N items) + for ($i = 0; $i < count($data['woo_data']); $i++) { + $woo_data = $data['woo_data'][$i]; + $facebook_data = $data['facebook_data'][$i]; + + if (!($facebook_data['found'] ?? false)) { + continue; // Skip products not found in Facebook + // these are logged in checkSyncStatus as missing variations + } + + $compared_products++; + + // Use the consistent id field from woo_data for both simple and variable products + $product_id = $woo_data['id'] ?? $this->product_id; + + $product_mismatches = $this->compareProductFields( + $woo_data, + $facebook_data, + $product_id + ); + + if (count($product_mismatches) > 0) { + $mismatches = array_merge($mismatches, $product_mismatches); + $this->debug("Found mismatches for product/variation - {$product_id}"); + } + } + + $this->result['mismatches'] = $mismatches; + $this->debug("Compared fields for {$compared_products} products, found " . count($mismatches) . " total mismatches"); + } + + /** + * Compare fields for a single product + */ + private function compareProductFields($woo_data, $facebook_data, $product_id) { + $mismatches = []; + + foreach (self::FIELD_MAPPINGS as $woo_field => $fb_field) { + $woo_value = $woo_data[$woo_field] ?? ''; + $fb_value = $facebook_data[$fb_field] ?? ''; + + $normalized_woo = $this->normalizeValue($woo_value, $woo_field); + $normalized_fb = $this->normalizeValue($fb_value, $woo_field); + + if ($normalized_woo !== $normalized_fb) { + $this->debug("MISMATCH {$woo_field}: WooCommerce='{$woo_value}' (normalized='{$normalized_woo}') vs Facebook='{$fb_value}' (normalized='{$normalized_fb}')"); + + $mismatches["{$product_id}_{$woo_field}"] = [ + 'product_id' => $product_id, + 'field' => $woo_field, + 'woocommerce_value' => $woo_value, + 'facebook_value' => $fb_value + ]; + } + } + + return $mismatches; + } + + /** + * Helper function to truncate text with ellipsis + */ + private function truncateText($text, $length) { + if (strlen($text) <= $length) { + return $text; + } + return substr($text, 0, $length) . '...'; + } + + private function normalizeValue($value, $field = '') { + $normalized = trim(strtolower((string)$value)); + + // Special handling for price fields + if ($field === 'price') { + return $this->normalizePrice($normalized); + } + + return $normalized; + } + + /** + * Normalize price values to handle different currency formats + * Examples: + * "34 GBP" -> "34.00" + * "ยฃ34.00" -> "34.00" + * "$25.99" -> "25.99" + * "19.99 USD" -> "19.99" + */ + private function normalizePrice($price) { + if (empty($price)) return ''; + + // Remove currency symbols and codes + $price = preg_replace('/[^\d.,]/', '', (string)$price); + $price = preg_replace('/,(?=\d{3,})/', '', $price); // Remove thousands separators + $price = str_replace(',', '.', $price); // Convert comma decimals + + return is_numeric($price) ? number_format((float)$price, 2, '.', '') : $price; + } + + public function getJsonResult() { + return json_encode($this->result, JSON_PRETTY_PRINT); + } + + public static function validateProduct($product_id, $wait_seconds = 5) { + $validator = new self($product_id, $wait_seconds); + return $validator->validate(); + } +} + +// Main execution when called directly +if (php_sapi_name() === 'cli') { + try { + $product_id = isset($argv[1]) ? (int)$argv[1] : null; + $wait_seconds = isset($argv[2]) ? (int)$argv[2] : 10; + + if (!$product_id) { + echo json_encode(['success' => false, 'error' => 'Product ID required']); + exit(1); + } + + $validator = new FacebookSyncValidator($product_id, $wait_seconds); + $result = $validator->validate(); + echo $validator->getJsonResult(); + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage(), + 'debug' => ["Exception: " . $e->getMessage()] + ]); + exit(1); + } +} diff --git a/tests/e2e/product-creation.spec.js b/tests/e2e/product-creation.spec.js index 1e54cb997..b9a43c654 100644 --- a/tests/e2e/product-creation.spec.js +++ b/tests/e2e/product-creation.spec.js @@ -9,21 +9,21 @@ const password = process.env.WP_PASSWORD || 'admin'; async function loginToWordPress(page) { // Navigate to login page await page.goto(`${baseURL}/wp-admin/`, { waitUntil: 'networkidle', timeout: 120000 }); - + // Check if we're already logged in const isLoggedIn = await page.locator('#wpcontent').isVisible({ timeout: 5000 }); if (isLoggedIn) { console.log('โœ… Already logged in'); return; } - + // Fill login form - wait longer for login elements console.log('๐Ÿ” Logging in to WordPress...'); await page.waitForSelector('#user_login', { timeout: 120000 }); await page.fill('#user_login', username); await page.fill('#user_pass', password); await page.click('#wp-submit'); - + // Wait for login to complete await page.waitForLoadState('networkidle', { timeout: 120000 }); console.log('โœ… Login completed'); @@ -44,40 +44,181 @@ async function safeScreenshot(page, path) { } } +// cleanup function - Delete created product from WooCommerce +async function cleanupProduct(productId) { + if (!productId) return; + + console.log(`๐Ÿงน Cleaning up product ${productId}...`); + + try { + const { exec } = require('child_process'); + const { promisify } = require('util'); + const execAsync = promisify(exec); + + const { stdout } = await execAsync( + `php -r "require_once('/tmp/wordpress/wp-load.php'); wp_delete_post(${productId}, true);"`, + { cwd: __dirname } + ); + + console.log(`โœ… Product ${productId} deleted from WooCommerce`); + } catch (error) { + console.log(`โš ๏ธ Cleanup failed: ${error.message}`); + } +} + +// Helper function to generate product name with timestamp and instance ID +function generateProductName(productType) { + const now = new Date(); + const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19); + const runId = process.env.GITHUB_RUN_ID || 'local'; + return `Test ${productType} Product E2E ${timestamp}-${runId}`; +} + +// Helper function to generate unique SKU for any product type +function generateUniqueSKU(productType) { + const runId = process.env.GITHUB_RUN_ID || 'local'; + const randomSuffix = Math.random().toString(36).substring(2, 8); + return `E2E-${productType.toUpperCase()}-${runId}-${randomSuffix}`; +} + +// Helper function to extract product ID from URL +function extractProductIdFromUrl(url) { + const urlMatch = url.match(/post=(\d+)/); + const productId = urlMatch ? parseInt(urlMatch[1]) : null; + console.log(`โœ… Extracted Product ID: ${productId}`); + return productId; +} + +// Helper function to publish product +async function publishProduct(page) { + try { + await page.locator('#publishing-action').scrollIntoViewIfNeeded(); + const publishButton = page.locator('#publish'); + if (await publishButton.isVisible({ timeout: 120000 })) { + await publishButton.click(); + await page.waitForTimeout(3000); + console.log('โœ… Published product'); + return true; + } + } catch (error) { + console.log('โš ๏ธ Publish step may be slow, continuing with error check'); + return false; + } +} + +// Helper function to check for PHP errors +async function checkForPhpErrors(page) { + const pageContent = await page.content(); + expect(pageContent).not.toContain('Fatal error'); + expect(pageContent).not.toContain('Parse error'); +} + +// Helper function to wait for manual inspection +async function waitForManualInspection(page, seconds = 60) { + console.log(`โณ Waiting ${seconds} seconds to allow manual catalog inspection...`); + await page.waitForTimeout(seconds * 1000); +} + +// Helper function to mark test start +function logTestStart(testInfo) { + const testName = testInfo.title; + console.log('\n' + '='.repeat(80)); + console.log(`๐Ÿš€ STARTING TEST: ${testName}`); + console.log('='.repeat(80)); +} + +// Helper function to mark test end +function logTestEnd(testInfo, success = true) { + const testName = testInfo.title; + console.log('='.repeat(80)); + if (success) { + console.log(`โœ… TEST SUCCESS: ${testName} โœ…`); + } else { + console.log(`โŒ TEST FAILED: ${testName}`); + } + console.log('='.repeat(80) + '\n'); +} + +// Helper function to validate Facebook sync +async function validateFacebookSync(productId, productName, waitSeconds = 10) { + if (!productId) { + console.log('โš ๏ธ No product ID provided for Facebook sync validation'); + return null; + } + + const displayName = productName ? `"${productName}" (ID: ${productId})` : `ID: ${productId}`; + console.log(`๐Ÿ” Validating Facebook sync for product ${displayName}...`); + + try { + const { exec } = require('child_process'); + const { promisify } = require('util'); + const execAsync = promisify(exec); + + // Call the Facebook sync validator + const { stdout, stderr } = await execAsync( + `php e2e-facebook-sync-validator.php ${productId} ${waitSeconds}`, + { cwd: __dirname } + ); + + // ๐Ÿ“„ DUMP RAW JSON OUTPUT FROM VALIDATOR + console.log('๐Ÿ“„ OUTPUT FROM FACEBOOK SYNC VALIDATOR:'); + console.log(stdout); + + const result = JSON.parse(stdout); + + // Display results + if (result.success) { + console.log(`๐ŸŽ‰ Facebook Sync Validation Succeeded for ${displayName}:`); + } else { + console.log(`โŒ Facebook sync validation Failed: ${result.error}. Check debug logs above.`); + } + + return result; + + } catch (error) { + console.log(`โš ๏ธ Facebook sync validation error: ${error.message}`); + return null; + } +} + test.describe('Facebook for WooCommerce - Product Creation E2E Tests', () => { - - test.beforeEach(async ({ page }) => { + + test.beforeEach(async ({ page }, testInfo) => { + // Log test start first for proper chronological order + logTestStart(testInfo); + // Ensure browser stability await page.setViewportSize({ width: 1280, height: 720 }); await loginToWordPress(page); }); - test('Create simple product with WooCommerce', async ({ page }) => { + + test('Create simple product with WooCommerce', async ({ page }, testInfo) => { + let productId = null; try { - await loginToWordPress(page); - + // Navigate to add new product page - await page.goto(`${baseURL}/wp-admin/post-new.php?post_type=product`, { - waitUntil: 'networkidle', - timeout: 120000 + await page.goto(`${baseURL}/wp-admin/post-new.php?post_type=product`, { + waitUntil: 'networkidle', + timeout: 120000 }); - + // Wait for the product editor to load await page.waitForSelector('#title', { timeout: 120000 }); - - // Fill product details - await page.fill('#title', 'Test Simple Product - E2E'); - + + const productName = generateProductName('Simple'); + await page.fill('#title', productName); + // Try to add content - handle different editor types try { console.log('๐Ÿ”„ Attempting to add product description...'); - + // First, try the visual/TinyMCE editor const visualTab = page.locator('#content-tmce'); if (await visualTab.isVisible({ timeout: 5000 })) { await visualTab.click(); await page.waitForTimeout(2000); - + // Check if TinyMCE iframe exists const tinyMCEFrame = page.locator('#content_ifr'); if (await tinyMCEFrame.isVisible({ timeout: 5000 })) { @@ -95,7 +236,7 @@ test.describe('Facebook for WooCommerce - Product Creation E2E Tests', () => { if (await textTab.isVisible({ timeout: 5000 })) { await textTab.click(); await page.waitForTimeout(1000); - + // Regular textarea const contentTextarea = page.locator('#content'); if (await contentTextarea.isVisible({ timeout: 5000 })) { @@ -117,26 +258,38 @@ test.describe('Facebook for WooCommerce - Product Creation E2E Tests', () => { } catch (editorError) { console.log(`โš ๏ธ Content editor issue: ${editorError.message} - continuing without description`); } - + console.log('โœ… Basic product details filled'); - + // Scroll to product data section await page.locator('#woocommerce-product-data').scrollIntoViewIfNeeded(); - + + // Click on Inventory tab + await page.click('li.inventory_tab a'); + await page.waitForTimeout(1000); // Wait for tab content to load + + // Set SKU to ensure unique retailer ID + const skuField = page.locator('#_sku'); + if (await skuField.isVisible({ timeout: 120000 })) { + const uniqueSku = generateUniqueSKU('simple'); + await skuField.fill(uniqueSku); + console.log(`โœ… Set unique SKU: ${uniqueSku}`); + } + // Set regular price const regularPriceField = page.locator('#_regular_price'); if (await regularPriceField.isVisible({ timeout: 120000 })) { await regularPriceField.fill('19.99'); console.log('โœ… Set regular price'); } - + // Look for Facebook-specific fields if plugin is active try { // Check various possible Facebook field selectors const facebookSyncField = page.locator('#_facebook_sync_enabled, input[name*="facebook"], input[id*="facebook"]').first(); const facebookPriceField = page.locator('label:has-text("Facebook Price"), input[name*="facebook_price"]').first(); const facebookImageField = page.locator('legend:has-text("Facebook Product Image"), input[name*="facebook_image"]').first(); - + if (await facebookSyncField.isVisible({ timeout: 10000 })) { console.log('โœ… Facebook for WooCommerce fields detected'); } else if (await facebookPriceField.isVisible({ timeout: 10000 })) { @@ -149,326 +302,218 @@ test.describe('Facebook for WooCommerce - Product Creation E2E Tests', () => { } catch (error) { console.log('โš ๏ธ Facebook field detection inconclusive - this is not necessarily an error'); } - + // Set product status to published and save - try { - // Look for publish/update button - await page.locator('#publishing-action').scrollIntoViewIfNeeded(); - - const publishButton = page.locator('#publish'); - if (await publishButton.isVisible({ timeout: 120000 })) { - await publishButton.click(); - await page.waitForTimeout(3000); - console.log('โœ… Published simple product'); - } - } catch (error) { - console.log('โš ๏ธ Publish step may be slow, continuing with error check'); - } - + // Publish product + await publishProduct(page); + + // Extract product ID from URL after publish + const currentUrl = page.url(); + productId = extractProductIdFromUrl(currentUrl); + // Verify no PHP fatal errors - const pageContent = await page.content(); - expect(pageContent).not.toContain('Fatal error'); - expect(pageContent).not.toContain('Parse error'); - + await checkForPhpErrors(page); + + // Validate sync to Meta catalog and fields from Meta + const result = await validateFacebookSync(productId, productName); + expect(result['success']).toBe(true); + console.log('โœ… Simple product creation test completed successfully'); - + // await waitForManualInspection(page); + + logTestEnd(testInfo, true); + } catch (error) { console.log(`โš ๏ธ Simple product test failed: ${error.message}`); // Take screenshot for debugging await safeScreenshot(page, 'simple-product-test-failure.png'); + logTestEnd(testInfo, false); throw error; + } finally { + // Cleanup irrespective of test result + if (productId) { + await cleanupProduct(productId); } + } }); - test('Create variable product with attributes - comprehensive test', async ({ page }) => { + test('Create variable product with WooCommerce', async ({ page }, testInfo) => { + let productId = null; try { - await loginToWordPress(page); - - // Navigate to add new product page - await page.goto(`${baseURL}/wp-admin/post-new.php?post_type=product`, { - waitUntil: 'networkidle', - timeout: 120000 - }); - - // Wait for the product editor to load - await page.waitForSelector('#title', { timeout: 120000 }); - await page.fill('#title', 'Test Variable Product - E2E'); - - // Set product type to variable - await page.selectOption('#product-type', 'variable'); - console.log('โœ… Set product type to variable'); - - // Wait for the page to process the product type change - await page.waitForTimeout(5000); - - // Wait for variable product options to become visible - more robust approach - console.log('๐Ÿ”„ Waiting for variable product interface to load...'); - try { - // Try multiple selectors and approaches - const selectors = [ - '#variable_product_options:not([style*="display: none"])', - '.product_data_tabs li a[href="#product_attributes"]', - '#product_attributes-tab', - '.woocommerce_attributes' - ]; - - let interfaceLoaded = false; - for (const selector of selectors) { - try { - await page.waitForSelector(selector, { timeout: 30000 }); - console.log(`โœ… Found interface element: ${selector}`); - interfaceLoaded = true; - break; - } catch (err) { - console.log(`โš ๏ธ Selector ${selector} not found, trying next...`); - } - } - - if (!interfaceLoaded) { - // Force refresh and try again - console.log('๐Ÿ”„ Interface not loaded, refreshing page...'); - await page.reload({ waitUntil: 'networkidle', timeout: 120000 }); - await page.waitForSelector('#title', { timeout: 120000 }); - await page.selectOption('#product-type', 'variable'); - await page.waitForTimeout(5000); - } - } catch (error) { - console.log(`โš ๏ธ Variable product interface loading issue: ${error.message}`); - } - console.log('โœ… Variable product interface loaded'); - - // Go to Attributes tab - try multiple approaches - console.log('๐Ÿ”„ Navigating to Attributes tab...'); - try { - // First, ensure we're in the right context and wait for the product data tabs to be ready - await page.waitForSelector('.product_data_tabs', { timeout: 30000 }); - - // Use more specific selectors to avoid conflicts - const attributesTab = page.locator('.product_data_tabs li:has(a[href="#product_attributes"]) a'); - - // Wait for the tab to be visible and clickable - await attributesTab.waitFor({ state: 'visible', timeout: 30000 }); - await attributesTab.click(); - await page.waitForTimeout(2000); - - // Verify the attributes panel is now visible - await page.waitForSelector('#product_attributes', { state: 'visible', timeout: 15000 }); - console.log('โœ… Successfully navigated to Attributes tab'); - } catch (error) { - console.log(`โš ๏ธ Attributes tab navigation issue: ${error.message}`); - // Fallback: try direct click on any visible attributes link - try { - await page.locator('text=Attributes').first().click(); - await page.waitForTimeout(2000); - } catch (fallbackError) { - console.log(`โš ๏ธ Fallback attributes tab click failed: ${fallbackError.message}`); - } - } - console.log('โœ… Switched to Attributes tab'); - - try { - // Add Size attribute - more robust approach - console.log('๐Ÿ”„ Adding product attribute...'); - - // Wait for attributes section to be visible - await page.waitForSelector('#product_attributes', { state: 'visible', timeout: 30000 }); - - // Try to add attribute using the dropdown - const attributeTaxonomy = page.locator('#attribute_taxonomy'); - await attributeTaxonomy.waitFor({ state: 'visible', timeout: 15000 }); - await attributeTaxonomy.selectOption({ label: 'Custom product attribute' }); - - const addAttributeBtn = page.locator('button.add_attribute'); - await addAttributeBtn.waitFor({ state: 'visible', timeout: 10000 }); - await addAttributeBtn.click(); - await page.waitForTimeout(3000); - - // Fill attribute details - wait for the new attribute row to appear - await page.waitForSelector('.woocommerce_attribute', { timeout: 10000 }); - - const nameField = page.locator('input[name="attribute_names[0]"]').first(); - const valueField = page.locator('textarea[name="attribute_values[0]"]').first(); - const variationCheckbox = page.locator('input[name="attribute_variation[0]"]').first(); - - await nameField.waitFor({ state: 'visible', timeout: 10000 }); - await nameField.fill('Size'); - console.log('โœ… Filled attribute name'); - - await valueField.waitFor({ state: 'visible', timeout: 10000 }); - await valueField.fill('Small | Medium | Large'); - console.log('โœ… Filled attribute values'); - - await variationCheckbox.waitFor({ state: 'visible', timeout: 10000 }); - await variationCheckbox.check(); - console.log('โœ… Checked variation checkbox'); - - // Save attributes - const saveAttributesBtn = page.locator('button.save_attributes'); - await saveAttributesBtn.waitFor({ state: 'visible', timeout: 10000 }); - await saveAttributesBtn.click(); - await page.waitForTimeout(5000); - console.log('โœ… Saved attributes'); - - console.log('โœ… Added Size attribute with variations'); - - // Go to Variations tab - console.log('๐Ÿ”„ Navigating to Variations tab...'); - - // Wait for variations tab to become available (after saving attributes) - await page.waitForTimeout(2000); - - const variationsTab = page.locator('.product_data_tabs li:has(a[href="#variable_product_options"]) a'); - await variationsTab.waitFor({ state: 'visible', timeout: 30000 }); - await variationsTab.click(); - await page.waitForTimeout(2000); - - // Verify the variations panel is now visible - await page.waitForSelector('#variable_product_options', { state: 'visible', timeout: 15000 }); - console.log('โœ… Successfully navigated to Variations tab'); - - // Generate variations from all attributes - simplified approach - console.log('๐Ÿ”„ Attempting to generate variations...'); - try { - // Wait for the variations interface to load - await page.waitForTimeout(2000); - - // Look for variation generation controls - try multiple selectors - const variationActions = page.locator('.toolbar .variation_actions select'); - await variationActions.waitFor({ state: 'visible', timeout: 15000 }); - - // Select "Create variations from all attributes" - await variationActions.selectOption('add_variation'); - - // Click the "Go" button - const goButton = page.locator('.toolbar .do_variation_action'); - await goButton.waitFor({ state: 'visible', timeout: 10000 }); - await goButton.click(); - await page.waitForTimeout(10000); - console.log('โœ… Generated product variations'); - - // Set prices for variations if they exist - await page.waitForTimeout(3000); - const variations = await page.locator('.woocommerce_variation').count(); - console.log(`Found ${variations} variations`); - - if (variations > 0) { - console.log(`โœ… Found ${variations} variations, setting prices...`); - - for (let i = 0; i < Math.min(variations, 2); i++) { - try { - const variation = page.locator('.woocommerce_variation').nth(i); - - // Expand variation if needed - const expandBtn = variation.locator('.expand_variation'); - if (await expandBtn.isVisible({ timeout: 5000 })) { - await expandBtn.click(); - await page.waitForTimeout(2000); - } - - // Set price - const priceField = variation.locator('input[name*="variable_regular_price"]').first(); - if (await priceField.isVisible({ timeout: 10000 })) { - await priceField.fill(`${25 + i}.99`); - console.log(`โœ… Set price for variation ${i + 1}`); - } - } catch (priceError) { - console.log(`โš ๏ธ Could not set price for variation ${i + 1}: ${priceError.message}`); - } - } - - // Save variations - try { - const saveBtn = page.locator('button.save-variation-changes, .save-variation-changes'); - if (await saveBtn.isVisible({ timeout: 10000 })) { - await saveBtn.click(); - await page.waitForTimeout(5000); - console.log('โœ… Saved variation changes'); - } - } catch (saveError) { - console.log(`โš ๏ธ Could not save variations: ${saveError.message}`); - } - } else { - console.log('โš ๏ธ No variations found - this may be expected if attribute setup failed'); - } - } catch (variationError) { - console.log(`โš ๏ธ Variation generation issue: ${variationError.message}`); - } - } catch (error) { - console.log(`โš ๏ธ Variation setup warning: ${error.message}`); - } - - // Publish product - try { - console.log('๐Ÿ”„ Publishing product...'); - const publishButton = page.locator('#publish'); - if (await publishButton.isVisible({ timeout: 30000 })) { - await publishButton.click(); - await page.waitForTimeout(5000); - console.log('โœ… Published variable product'); - } - } catch (error) { - console.log('โš ๏ธ Publish step may be slow, continuing with error check'); - } - - // Verify no PHP fatal errors - const pageContent = await page.content(); - expect(pageContent).not.toContain('Fatal error'); - expect(pageContent).not.toContain('Parse error'); - - console.log('โœ… Variable product creation test completed successfully'); - - } catch (error) { - console.log(`โš ๏ธ Variable product test failed: ${error.message}`); - // Take screenshot for debugging - await safeScreenshot(page, 'variable-product-test-failure.png'); - throw error; + + // Step 1: Navigate to add new product + await page.goto(`${baseURL}/wp-admin/post-new.php?post_type=product`, { + waitUntil: 'networkidle', + timeout: 120000 + }); + + // Step 2: Fill product title + await page.waitForSelector('#title', { timeout: 120000 }); + const productName = generateProductName('Variable'); + await page.fill('#title', productName); + + // Step 2.1: Add product description (human-like interaction) + await page.click('#content-tmce'); // Click Visual tab + await page.waitForTimeout(1000); + const frameContent = page.locator('#content_ifr').contentFrame(); + await frameContent.locator('body').click(); // Click in the editor + await frameContent.locator('body').type('This is a test variable product with multiple variations.'); + + // Set up dialog handler for WooCommerce tour popup + page.on('dialog', async dialog => { + await dialog.accept(); + console.log('โœ… Dialog accepted'); + }); + + // Step 3: Set product type to variable + await page.selectOption('#product-type', 'variable'); + console.log('โœ… Set product type to variable'); + + // Step 3.5: Set unique SKU for parent product + const uniqueParentSku = generateUniqueSKU('variable'); + await page.locator('#_sku').fill(uniqueParentSku); + console.log(`โœ… Set unique parent SKU: ${uniqueParentSku}`); + + // Step 4: Tell browser to directly click popup + await page.evaluate(() => document.querySelector('button.woocommerce-tour-kit-step-navigation__done-btn')?.click()); + + // Step 5: Add attributes + // Go to Attributes tab + await page.click('li.attribute_tab a[href="#product_attributes"]'); + await page.waitForTimeout(2000); + // Add name & value + await page.fill('input.attribute_name[name="attribute_names[0]"]', 'Color'); + await page.fill('textarea[name="attribute_values[0]"]', 'Red|Blue|Green'); + // Use tab to enable Save Attributes button + await page.locator('#product_attributes .woocommerce_attribute textarea[name^="attribute_values"]').press('Tab'); + await page.click('button.save_attributes.button-primary'); + await page.waitForTimeout(5000); + console.log('โœ… Saved attributes'); + + // Step 6: Generate variations + // Go to Variations tab + await page.click('a[href="#variable_product_options"]'); + await page.waitForTimeout(2000); + // Click "Generate variations" button + await page.click('button.generate_variations'); + await page.waitForTimeout(8000); + // Verify variations were created + const variationsCount = await page.locator('.woocommerce_variation').count(); + console.log(`โœ… Generated ${variationsCount} variations`); + + if (variationsCount > 0) { + // Step 7: Set prices for variations + // Click "Add price" button first + const addPriceBtn = page.locator('button.add_price_for_variations'); + await addPriceBtn.waitFor({ state: 'visible', timeout: 10000 }); + await addPriceBtn.click(); + console.log('โœ… Clicked "Add price" button'); + + // Wait for price input field to appear + await page.waitForTimeout(2000); + + // Add bulk price + const priceInput = page.locator('input.components-text-control__input.wc_input_variations_price'); + await priceInput.waitFor({ state: 'visible', timeout: 10000 }); + await priceInput.click(); // โœ… Focus the field + await priceInput.clear(); // โœ… Clear existing content + await priceInput.type('29.99', { delay: 100 }); // โœ… Type with delays = triggers all JS events + + // Click "Add prices" button to apply the price + const addPricesBtn = page.locator('button.add_variations_price_button.button-primary'); + await addPricesBtn.waitFor({ state: 'visible', timeout: 10000 }); + await addPricesBtn.click(); + await page.waitForTimeout(3000); + console.log('โœ… Bulk price added successfully'); } - }); - test('Test WordPress admin and Facebook plugin presence', async ({ page }) => { + // Step 8: Publish product + await page.click('#publish'); + await page.waitForTimeout(5000); + // Verify success + const pageContent = await page.content(); + expect(pageContent).not.toContain('Fatal error'); + expect(pageContent).not.toContain('Parse error'); + + console.log('โœ… Variable product created successfully!'); + + // Extract product ID from URL after publish + const currentUrl = page.url(); + productId = extractProductIdFromUrl(currentUrl); + + // Verify no PHP fatal errors + await checkForPhpErrors(page); + + // Validate sync to Meta catalog and fields from Meta + const result = await validateFacebookSync(productId, productName,20); + expect(result['success']).toBe(true); + + // await waitForManualInspection(page); + + logTestEnd(testInfo, true); + + } catch (error) { + console.log(`โŒ Variable product test failed: ${error.message}`); + logTestEnd(testInfo, false); + await safeScreenshot(page, 'variable-product-test-failure.png'); + throw error; + } + finally { + // Cleanup irrespective of test result + if (productId) { + await cleanupProduct(productId); + } + } +}); + + test('Test WordPress admin and Facebook plugin presence', async ({ page }, testInfo) => { + try { // Navigate to plugins page with increased timeout - await page.goto(`${baseURL}/wp-admin/plugins.php`, { - waitUntil: 'networkidle', - timeout: 120000 + await page.goto(`${baseURL}/wp-admin/plugins.php`, { + waitUntil: 'networkidle', + timeout: 120000 }); - + // Check if Facebook plugin is listed const pageContent = await page.content(); - const hasFacebookPlugin = pageContent.includes('Facebook for WooCommerce') || + const hasFacebookPlugin = pageContent.includes('Facebook for WooCommerce') || pageContent.includes('facebook-for-woocommerce'); - + if (hasFacebookPlugin) { console.log('โœ… Facebook for WooCommerce plugin detected'); } else { console.log('โš ๏ธ Facebook for WooCommerce plugin not found in plugins list'); } - + // Verify no PHP errors expect(pageContent).not.toContain('Fatal error'); expect(pageContent).not.toContain('Parse error'); - + console.log('โœ… Plugin detection test completed'); - + logTestEnd(testInfo, true); + } catch (error) { console.log(`โš ๏ธ Plugin detection test failed: ${error.message}`); + logTestEnd(testInfo, false); throw error; } }); - test('Test basic WooCommerce product list', async ({ page }) => { + test('Test basic WooCommerce product list', async ({ page }, testInfo) => { + try { // Go to Products list with increased timeout - await page.goto(`${baseURL}/wp-admin/edit.php?post_type=product`, { - waitUntil: 'networkidle', - timeout: 120000 + await page.goto(`${baseURL}/wp-admin/edit.php?post_type=product`, { + waitUntil: 'networkidle', + timeout: 120000 }); - + // Verify no PHP errors on products page const pageContent = await page.content(); expect(pageContent).not.toContain('Fatal error'); expect(pageContent).not.toContain('Parse error'); - + // Check if WooCommerce is working const hasProductsTable = await page.locator('.wp-list-table').isVisible({ timeout: 120000 }); if (hasProductsTable) { @@ -476,67 +521,77 @@ test.describe('Facebook for WooCommerce - Product Creation E2E Tests', () => { } else { console.log('โš ๏ธ Products table not found'); } - + console.log('โœ… Product list test completed'); - + logTestEnd(testInfo, true); + } catch (error) { console.log(`โš ๏ธ Product list test failed: ${error.message}`); + logTestEnd(testInfo, false); throw error; } }); - test('Quick PHP error check across key pages', async ({ page }) => { - const pagesToCheck = [ - { path: '/wp-admin/', name: 'Dashboard' }, - { path: '/wp-admin/edit.php?post_type=product', name: 'Products' }, - { path: '/wp-admin/plugins.php', name: 'Plugins' } - ]; - - for (const pageInfo of pagesToCheck) { - try { - console.log(`๐Ÿ” Checking ${pageInfo.name} page...`); - await page.goto(`${baseURL}${pageInfo.path}`, { - waitUntil: 'networkidle', - timeout: 120000 - }); - - const pageContent = await page.content(); - - // Check for PHP errors - expect(pageContent).not.toContain('Fatal error'); - expect(pageContent).not.toContain('Parse error'); - expect(pageContent).not.toContain('Warning: '); - - // Verify admin content loaded - await page.locator('#wpcontent').isVisible({ timeout: 120000 }); - - console.log(`โœ… ${pageInfo.name} page loaded without errors`); - - } catch (error) { - console.log(`โš ๏ธ ${pageInfo.name} page check failed: ${error.message}`); + test('Quick PHP error check across key pages', async ({ page }, testInfo) => { + + try { + const pagesToCheck = [ + { path: '/wp-admin/', name: 'Dashboard' }, + { path: '/wp-admin/edit.php?post_type=product', name: 'Products' }, + { path: '/wp-admin/plugins.php', name: 'Plugins' } + ]; + + for (const pageInfo of pagesToCheck) { + try { + console.log(`๐Ÿ” Checking ${pageInfo.name} page...`); + await page.goto(`${baseURL}${pageInfo.path}`, { + waitUntil: 'networkidle', + timeout: 120000 + }); + + const pageContent = await page.content(); + + // Check for PHP errors + expect(pageContent).not.toContain('Fatal error'); + expect(pageContent).not.toContain('Parse error'); + expect(pageContent).not.toContain('Warning: '); + + // Verify admin content loaded + await page.locator('#wpcontent').isVisible({ timeout: 120000 }); + + console.log(`โœ… ${pageInfo.name} page loaded without errors`); + + } catch (error) { + console.log(`โš ๏ธ ${pageInfo.name} page check failed: ${error.message}`); + } } + + logTestEnd(testInfo, true); + } catch (error) { + logTestEnd(testInfo, false); + throw error; } }); - test('Test Facebook plugin deactivation and reactivation', async ({ page }) => { + test('Test Facebook plugin deactivation and reactivation', async ({ page }, testInfo) => { + try { - await loginToWordPress(page); - + // Navigate to plugins page - await page.goto(`${baseURL}/wp-admin/plugins.php`, { - waitUntil: 'networkidle', - timeout: 120000 + await page.goto(`${baseURL}/wp-admin/plugins.php`, { + waitUntil: 'networkidle', + timeout: 120000 }); - + // Look for Facebook plugin row const pluginRow = page.locator('tr[data-slug="facebook-for-woocommerce"], tr:has-text("Facebook for WooCommerce")').first(); - + if (await pluginRow.isVisible({ timeout: 120000 })) { console.log('โœ… Facebook plugin found'); - + // Check if plugin is currently active const isActive = await pluginRow.locator('.active').isVisible({ timeout: 120000 }); - + if (isActive) { console.log('Plugin is active, testing deactivation...'); const deactivateLink = pluginRow.locator('a:has-text("Deactivate")'); @@ -544,7 +599,7 @@ test.describe('Facebook for WooCommerce - Product Creation E2E Tests', () => { await deactivateLink.click(); await page.waitForTimeout(2000); console.log('โœ… Plugin deactivated'); - + // Now test reactivation await page.waitForTimeout(1000); const reactivateLink = pluginRow.locator('a:has-text("Activate")'); @@ -566,16 +621,18 @@ test.describe('Facebook for WooCommerce - Product Creation E2E Tests', () => { } else { console.log('โš ๏ธ Facebook plugin not found in plugins list'); } - + // Verify no PHP errors after plugin operations const pageContent = await page.content(); expect(pageContent).not.toContain('Fatal error'); expect(pageContent).not.toContain('Parse error'); - + console.log('โœ… Plugin activation test completed'); - + logTestEnd(testInfo, true); + } catch (error) { console.log(`โš ๏ธ Plugin activation test failed: ${error.message}`); + logTestEnd(testInfo, false); throw error; } });