diff --git a/README.md b/README.md index 71ea626..270dbf2 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,12 @@ cremote [options] - `switch-iframe`: Switch to iframe context for subsequent commands - `switch-main`: Switch back to main page context - `list-tabs`: List all open tabs +- `disable-cache`: Disable browser cache for a tab +- `enable-cache`: Enable browser cache for a tab +- `clear-cache`: Clear browser cache for a tab +- `clear-all-site-data`: Clear all site data (cookies, storage, cache, etc.) +- `clear-cookies`: Clear cookies for a tab +- `clear-storage`: Clear web storage (localStorage, sessionStorage, etc.) - `status`: Check if the daemon is running ### Current Tab Feature @@ -304,6 +310,43 @@ cremote list-tabs This will display all open tabs with their IDs and URLs. The current tab is marked with an asterisk (*) +#### Cache and Site Data Management + +You can control browser cache and site data for testing, performance optimization, and privacy: + +```bash +# Cache Management +# Disable cache for current tab (useful for testing) +cremote disable-cache [--tab=""] [--timeout=5] + +# Enable cache for current tab +cremote enable-cache [--tab=""] [--timeout=5] + +# Clear browser cache for current tab +cremote clear-cache [--tab=""] [--timeout=5] + +# Site Data Management +# Clear ALL site data (cookies, storage, cache, etc.) +cremote clear-all-site-data [--tab=""] [--timeout=10] + +# Clear only cookies +cremote clear-cookies [--tab=""] [--timeout=5] + +# Clear only web storage (localStorage, sessionStorage, IndexedDB, etc.) +cremote clear-storage [--tab=""] [--timeout=5] +``` + +**Use Cases:** +- **Testing**: Disable cache to ensure fresh page loads without cached resources +- **Performance Testing**: Clear cache to test cold load performance +- **Debugging**: Clear cache to resolve cache-related issues +- **Development**: Disable cache during development to see changes immediately +- **Authentication Testing**: Clear cookies to test login/logout flows +- **Privacy Testing**: Clear all site data to test clean state scenarios +- **Storage Testing**: Clear web storage to test application state management + +The `--timeout` parameter specifies how many seconds to wait for the operation to complete (default: 5 seconds, use longer timeouts for comprehensive data clearing). + ### Connecting to a Remote Daemon By default, the client connects to a daemon running on localhost. To connect to a daemon running on a different host: diff --git a/client/client.go b/client/client.go index 1076f3a..84e1906 100644 --- a/client/client.go +++ b/client/client.go @@ -2453,3 +2453,171 @@ func (c *Client) ManageFiles(operation, pattern, maxAge string) (*FileManagement return &result, nil } + +// DisableCache disables browser cache for a tab +// If tabID is empty, the current tab will be used +// timeout is in seconds, 0 means no timeout +func (c *Client) DisableCache(tabID string, timeout int) error { + params := map[string]string{} + + // Only include tab ID if it's provided + if tabID != "" { + params["tab"] = tabID + } + + // Add timeout if specified + if timeout > 0 { + params["timeout"] = strconv.Itoa(timeout) + } + + resp, err := c.SendCommand("disable-cache", params) + if err != nil { + return err + } + + if !resp.Success { + return fmt.Errorf("failed to disable cache: %s", resp.Error) + } + + return nil +} + +// EnableCache enables browser cache for a tab +// If tabID is empty, the current tab will be used +// timeout is in seconds, 0 means no timeout +func (c *Client) EnableCache(tabID string, timeout int) error { + params := map[string]string{} + + // Only include tab ID if it's provided + if tabID != "" { + params["tab"] = tabID + } + + // Add timeout if specified + if timeout > 0 { + params["timeout"] = strconv.Itoa(timeout) + } + + resp, err := c.SendCommand("enable-cache", params) + if err != nil { + return err + } + + if !resp.Success { + return fmt.Errorf("failed to enable cache: %s", resp.Error) + } + + return nil +} + +// ClearCache clears browser cache for a tab +// If tabID is empty, the current tab will be used +// timeout is in seconds, 0 means no timeout +func (c *Client) ClearCache(tabID string, timeout int) error { + params := map[string]string{} + + // Only include tab ID if it's provided + if tabID != "" { + params["tab"] = tabID + } + + // Add timeout if specified + if timeout > 0 { + params["timeout"] = strconv.Itoa(timeout) + } + + resp, err := c.SendCommand("clear-cache", params) + if err != nil { + return err + } + + if !resp.Success { + return fmt.Errorf("failed to clear cache: %s", resp.Error) + } + + return nil +} + +// ClearAllSiteData clears all site data including cookies, storage, cache, etc. for a tab +// If tabID is empty, the current tab will be used +// timeout is in seconds, 0 means no timeout +func (c *Client) ClearAllSiteData(tabID string, timeout int) error { + params := map[string]string{} + + // Only include tab ID if it's provided + if tabID != "" { + params["tab"] = tabID + } + + // Add timeout if specified + if timeout > 0 { + params["timeout"] = strconv.Itoa(timeout) + } + + resp, err := c.SendCommand("clear-all-site-data", params) + if err != nil { + return err + } + + if !resp.Success { + return fmt.Errorf("failed to clear all site data: %s", resp.Error) + } + + return nil +} + +// ClearCookies clears cookies for a tab +// If tabID is empty, the current tab will be used +// timeout is in seconds, 0 means no timeout +func (c *Client) ClearCookies(tabID string, timeout int) error { + params := map[string]string{} + + // Only include tab ID if it's provided + if tabID != "" { + params["tab"] = tabID + } + + // Add timeout if specified + if timeout > 0 { + params["timeout"] = strconv.Itoa(timeout) + } + + resp, err := c.SendCommand("clear-cookies", params) + if err != nil { + return err + } + + if !resp.Success { + return fmt.Errorf("failed to clear cookies: %s", resp.Error) + } + + return nil +} + +// ClearStorage clears web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab +// If tabID is empty, the current tab will be used +// timeout is in seconds, 0 means no timeout +func (c *Client) ClearStorage(tabID string, timeout int) error { + params := map[string]string{} + + // Only include tab ID if it's provided + if tabID != "" { + params["tab"] = tabID + } + + // Add timeout if specified + if timeout > 0 { + params["timeout"] = strconv.Itoa(timeout) + } + + resp, err := c.SendCommand("clear-storage", params) + if err != nil { + return err + } + + if !resp.Success { + return fmt.Errorf("failed to clear storage: %s", resp.Error) + } + + return nil +} diff --git a/daemon/cremotedaemon b/daemon/cremotedaemon new file mode 100755 index 0000000..3cfdb1a Binary files /dev/null and b/daemon/cremotedaemon differ diff --git a/daemon/daemon.go b/daemon/daemon.go index c8dd30a..4bf28ee 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1035,6 +1035,120 @@ func (d *Daemon) handleCommand(w http.ResponseWriter, r *http.Request) { response = Response{Success: true, Data: result} } + case "disable-cache": + tabID := cmd.Params["tab"] + timeoutStr := cmd.Params["timeout"] + + // Parse timeout (default to 5 seconds if not specified) + timeout := 5 + if timeoutStr != "" { + if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 { + timeout = parsedTimeout + } + } + + err := d.setCacheDisabled(tabID, true, timeout) + if err != nil { + response = Response{Success: false, Error: err.Error()} + } else { + response = Response{Success: true} + } + + case "enable-cache": + tabID := cmd.Params["tab"] + timeoutStr := cmd.Params["timeout"] + + // Parse timeout (default to 5 seconds if not specified) + timeout := 5 + if timeoutStr != "" { + if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 { + timeout = parsedTimeout + } + } + + err := d.setCacheDisabled(tabID, false, timeout) + if err != nil { + response = Response{Success: false, Error: err.Error()} + } else { + response = Response{Success: true} + } + + case "clear-cache": + tabID := cmd.Params["tab"] + timeoutStr := cmd.Params["timeout"] + + // Parse timeout (default to 5 seconds if not specified) + timeout := 5 + if timeoutStr != "" { + if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 { + timeout = parsedTimeout + } + } + + err := d.clearBrowserCache(tabID, timeout) + if err != nil { + response = Response{Success: false, Error: err.Error()} + } else { + response = Response{Success: true} + } + + case "clear-all-site-data": + tabID := cmd.Params["tab"] + timeoutStr := cmd.Params["timeout"] + + // Parse timeout (default to 5 seconds if not specified) + timeout := 5 + if timeoutStr != "" { + if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 { + timeout = parsedTimeout + } + } + + err := d.clearAllSiteData(tabID, timeout) + if err != nil { + response = Response{Success: false, Error: err.Error()} + } else { + response = Response{Success: true} + } + + case "clear-cookies": + tabID := cmd.Params["tab"] + timeoutStr := cmd.Params["timeout"] + + // Parse timeout (default to 5 seconds if not specified) + timeout := 5 + if timeoutStr != "" { + if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 { + timeout = parsedTimeout + } + } + + err := d.clearCookies(tabID, timeout) + if err != nil { + response = Response{Success: false, Error: err.Error()} + } else { + response = Response{Success: true} + } + + case "clear-storage": + tabID := cmd.Params["tab"] + timeoutStr := cmd.Params["timeout"] + + // Parse timeout (default to 5 seconds if not specified) + timeout := 5 + if timeoutStr != "" { + if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 { + timeout = parsedTimeout + } + } + + err := d.clearStorage(tabID, timeout) + if err != nil { + response = Response{Success: false, Error: err.Error()} + } else { + response = Response{Success: true} + } + default: d.debugLog("Unknown action: %s", cmd.Action) response = Response{Success: false, Error: "Unknown action"} @@ -5271,3 +5385,289 @@ func (d *Daemon) queryAccessibilityTree(tabID, selector, accessibleName, role st d.debugLog("Successfully queried accessibility tree with %d matching nodes for tab: %s", len(axResult.Nodes), tabID) return &axResult, nil } + +// setCacheDisabled enables or disables browser cache for a tab +func (d *Daemon) setCacheDisabled(tabID string, disabled bool, timeout int) error { + d.debugLog("Setting cache disabled=%v for tab: %s with timeout: %d", disabled, tabID, timeout) + + // Use current tab if not specified + if tabID == "" { + tabID = d.currentTab + } + + if tabID == "" { + return fmt.Errorf("no tab specified and no current tab available") + } + + page, err := d.getTab(tabID) + if err != nil { + return fmt.Errorf("failed to get page: %v", err) + } + + // Create a context with timeout if specified + if timeout > 0 { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + // Create a channel to signal completion + done := make(chan error, 1) + + // Execute the cache setting in a goroutine + go func() { + err := proto.NetworkSetCacheDisabled{CacheDisabled: disabled}.Call(page) + done <- err + }() + + // Wait for completion or timeout + select { + case err := <-done: + if err != nil { + return fmt.Errorf("failed to set cache disabled: %v", err) + } + case <-ctx.Done(): + return fmt.Errorf("timeout setting cache disabled after %d seconds", timeout) + } + } else { + // No timeout - execute directly + err := proto.NetworkSetCacheDisabled{CacheDisabled: disabled}.Call(page) + if err != nil { + return fmt.Errorf("failed to set cache disabled: %v", err) + } + } + + d.debugLog("Successfully set cache disabled=%v for tab: %s", disabled, tabID) + return nil +} + +// clearBrowserCache clears the browser cache for a tab +func (d *Daemon) clearBrowserCache(tabID string, timeout int) error { + d.debugLog("Clearing browser cache for tab: %s with timeout: %d", tabID, timeout) + + // Use current tab if not specified + if tabID == "" { + tabID = d.currentTab + } + + if tabID == "" { + return fmt.Errorf("no tab specified and no current tab available") + } + + page, err := d.getTab(tabID) + if err != nil { + return fmt.Errorf("failed to get page: %v", err) + } + + // Create a context with timeout if specified + if timeout > 0 { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + // Create a channel to signal completion + done := make(chan error, 1) + + // Execute the cache clearing in a goroutine + go func() { + err := proto.NetworkClearBrowserCache{}.Call(page) + done <- err + }() + + // Wait for completion or timeout + select { + case err := <-done: + if err != nil { + return fmt.Errorf("failed to clear browser cache: %v", err) + } + case <-ctx.Done(): + return fmt.Errorf("timeout clearing browser cache after %d seconds", timeout) + } + } else { + // No timeout - execute directly + err := proto.NetworkClearBrowserCache{}.Call(page) + if err != nil { + return fmt.Errorf("failed to clear browser cache: %v", err) + } + } + + d.debugLog("Successfully cleared browser cache for tab: %s", tabID) + return nil +} + +// clearAllSiteData clears all site data including cookies, storage, cache, etc. for a tab +func (d *Daemon) clearAllSiteData(tabID string, timeout int) error { + d.debugLog("Clearing all site data for tab: %s with timeout: %d", tabID, timeout) + + // Use current tab if not specified + if tabID == "" { + tabID = d.currentTab + } + + if tabID == "" { + return fmt.Errorf("no tab specified and no current tab available") + } + + page, err := d.getTab(tabID) + if err != nil { + return fmt.Errorf("failed to get page: %v", err) + } + + // Create a context with timeout if specified + if timeout > 0 { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + // Create a channel to signal completion + done := make(chan error, 1) + + // Execute the site data clearing in a goroutine + go func() { + // Clear all types of site data + err := proto.StorageClearDataForOrigin{ + Origin: "*", // Clear for all origins + StorageTypes: "appcache,cookies,file_systems,indexeddb,local_storage,shader_cache,websql,service_workers,cache_storage", + }.Call(page) + done <- err + }() + + // Wait for completion or timeout + select { + case err := <-done: + if err != nil { + return fmt.Errorf("failed to clear all site data: %v", err) + } + case <-ctx.Done(): + return fmt.Errorf("timeout clearing all site data after %d seconds", timeout) + } + } else { + // No timeout - execute directly + err := proto.StorageClearDataForOrigin{ + Origin: "*", // Clear for all origins + StorageTypes: "appcache,cookies,file_systems,indexeddb,local_storage,shader_cache,websql,service_workers,cache_storage", + }.Call(page) + if err != nil { + return fmt.Errorf("failed to clear all site data: %v", err) + } + } + + d.debugLog("Successfully cleared all site data for tab: %s", tabID) + return nil +} + +// clearCookies clears cookies for a tab +func (d *Daemon) clearCookies(tabID string, timeout int) error { + d.debugLog("Clearing cookies for tab: %s with timeout: %d", tabID, timeout) + + // Use current tab if not specified + if tabID == "" { + tabID = d.currentTab + } + + if tabID == "" { + return fmt.Errorf("no tab specified and no current tab available") + } + + page, err := d.getTab(tabID) + if err != nil { + return fmt.Errorf("failed to get page: %v", err) + } + + // Create a context with timeout if specified + if timeout > 0 { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + // Create a channel to signal completion + done := make(chan error, 1) + + // Execute the cookie clearing in a goroutine + go func() { + // Clear cookies only + err := proto.StorageClearDataForOrigin{ + Origin: "*", // Clear for all origins + StorageTypes: "cookies", + }.Call(page) + done <- err + }() + + // Wait for completion or timeout + select { + case err := <-done: + if err != nil { + return fmt.Errorf("failed to clear cookies: %v", err) + } + case <-ctx.Done(): + return fmt.Errorf("timeout clearing cookies after %d seconds", timeout) + } + } else { + // No timeout - execute directly + err := proto.StorageClearDataForOrigin{ + Origin: "*", // Clear for all origins + StorageTypes: "cookies", + }.Call(page) + if err != nil { + return fmt.Errorf("failed to clear cookies: %v", err) + } + } + + d.debugLog("Successfully cleared cookies for tab: %s", tabID) + return nil +} + +// clearStorage clears web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab +func (d *Daemon) clearStorage(tabID string, timeout int) error { + d.debugLog("Clearing storage for tab: %s with timeout: %d", tabID, timeout) + + // Use current tab if not specified + if tabID == "" { + tabID = d.currentTab + } + + if tabID == "" { + return fmt.Errorf("no tab specified and no current tab available") + } + + page, err := d.getTab(tabID) + if err != nil { + return fmt.Errorf("failed to get page: %v", err) + } + + // Create a context with timeout if specified + if timeout > 0 { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + // Create a channel to signal completion + done := make(chan error, 1) + + // Execute the storage clearing in a goroutine + go func() { + // Clear storage types (excluding cookies and cache) + err := proto.StorageClearDataForOrigin{ + Origin: "*", // Clear for all origins + StorageTypes: "appcache,file_systems,indexeddb,local_storage,websql,service_workers,cache_storage", + }.Call(page) + done <- err + }() + + // Wait for completion or timeout + select { + case err := <-done: + if err != nil { + return fmt.Errorf("failed to clear storage: %v", err) + } + case <-ctx.Done(): + return fmt.Errorf("timeout clearing storage after %d seconds", timeout) + } + } else { + // No timeout - execute directly + err := proto.StorageClearDataForOrigin{ + Origin: "*", // Clear for all origins + StorageTypes: "appcache,file_systems,indexeddb,local_storage,websql,service_workers,cache_storage", + }.Call(page) + if err != nil { + return fmt.Errorf("failed to clear storage: %v", err) + } + } + + d.debugLog("Successfully cleared storage for tab: %s", tabID) + return nil +} diff --git a/main.go b/main.go index aa0d394..6a5b511 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,12 @@ func main() { screenshotCmd := flag.NewFlagSet("screenshot", flag.ExitOnError) statusCmd := flag.NewFlagSet("status", flag.ExitOnError) listTabsCmd := flag.NewFlagSet("list-tabs", flag.ExitOnError) + disableCacheCmd := flag.NewFlagSet("disable-cache", flag.ExitOnError) + enableCacheCmd := flag.NewFlagSet("enable-cache", flag.ExitOnError) + clearCacheCmd := flag.NewFlagSet("clear-cache", flag.ExitOnError) + clearAllSiteDataCmd := flag.NewFlagSet("clear-all-site-data", flag.ExitOnError) + clearCookiesCmd := flag.NewFlagSet("clear-cookies", flag.ExitOnError) + clearStorageCmd := flag.NewFlagSet("clear-storage", flag.ExitOnError) // Define flags for each subcommand // open-tab flags @@ -140,6 +146,42 @@ func main() { listTabsHost := listTabsCmd.String("host", "localhost", "Daemon host") listTabsPort := listTabsCmd.Int("port", 8989, "Daemon port") + // disable-cache flags + disableCacheTabID := disableCacheCmd.String("tab", "", "Tab ID to disable cache for (optional, uses current tab if not specified)") + disableCacheTimeout := disableCacheCmd.Int("timeout", 5, "Timeout in seconds for disabling cache") + disableCacheHost := disableCacheCmd.String("host", "localhost", "Daemon host") + disableCachePort := disableCacheCmd.Int("port", 8989, "Daemon port") + + // enable-cache flags + enableCacheTabID := enableCacheCmd.String("tab", "", "Tab ID to enable cache for (optional, uses current tab if not specified)") + enableCacheTimeout := enableCacheCmd.Int("timeout", 5, "Timeout in seconds for enabling cache") + enableCacheHost := enableCacheCmd.String("host", "localhost", "Daemon host") + enableCachePort := enableCacheCmd.Int("port", 8989, "Daemon port") + + // clear-cache flags + clearCacheTabID := clearCacheCmd.String("tab", "", "Tab ID to clear cache for (optional, uses current tab if not specified)") + clearCacheTimeout := clearCacheCmd.Int("timeout", 5, "Timeout in seconds for clearing cache") + clearCacheHost := clearCacheCmd.String("host", "localhost", "Daemon host") + clearCachePort := clearCacheCmd.Int("port", 8989, "Daemon port") + + // clear-all-site-data flags + clearAllSiteDataTabID := clearAllSiteDataCmd.String("tab", "", "Tab ID to clear all site data for (optional, uses current tab if not specified)") + clearAllSiteDataTimeout := clearAllSiteDataCmd.Int("timeout", 5, "Timeout in seconds for clearing all site data") + clearAllSiteDataHost := clearAllSiteDataCmd.String("host", "localhost", "Daemon host") + clearAllSiteDataPort := clearAllSiteDataCmd.Int("port", 8989, "Daemon port") + + // clear-cookies flags + clearCookiesTabID := clearCookiesCmd.String("tab", "", "Tab ID to clear cookies for (optional, uses current tab if not specified)") + clearCookiesTimeout := clearCookiesCmd.Int("timeout", 5, "Timeout in seconds for clearing cookies") + clearCookiesHost := clearCookiesCmd.String("host", "localhost", "Daemon host") + clearCookiesPort := clearCookiesCmd.Int("port", 8989, "Daemon port") + + // clear-storage flags + clearStorageTabID := clearStorageCmd.String("tab", "", "Tab ID to clear storage for (optional, uses current tab if not specified)") + clearStorageTimeout := clearStorageCmd.Int("timeout", 5, "Timeout in seconds for clearing storage") + clearStorageHost := clearStorageCmd.String("host", "localhost", "Daemon host") + clearStoragePort := clearStorageCmd.Int("port", 8989, "Daemon port") + // Check if a subcommand is provided if len(os.Args) < 2 { printUsage() @@ -490,6 +532,96 @@ func main() { } } + case "disable-cache": + disableCacheCmd.Parse(os.Args[2:]) + + // Create a client + c := client.NewClient(*disableCacheHost, *disableCachePort) + + // Disable cache + err := c.DisableCache(*disableCacheTabID, *disableCacheTimeout) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("Cache disabled successfully") + + case "enable-cache": + enableCacheCmd.Parse(os.Args[2:]) + + // Create a client + c := client.NewClient(*enableCacheHost, *enableCachePort) + + // Enable cache + err := c.EnableCache(*enableCacheTabID, *enableCacheTimeout) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("Cache enabled successfully") + + case "clear-cache": + clearCacheCmd.Parse(os.Args[2:]) + + // Create a client + c := client.NewClient(*clearCacheHost, *clearCachePort) + + // Clear cache + err := c.ClearCache(*clearCacheTabID, *clearCacheTimeout) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("Cache cleared successfully") + + case "clear-all-site-data": + clearAllSiteDataCmd.Parse(os.Args[2:]) + + // Create a client + c := client.NewClient(*clearAllSiteDataHost, *clearAllSiteDataPort) + + // Clear all site data + err := c.ClearAllSiteData(*clearAllSiteDataTabID, *clearAllSiteDataTimeout) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("All site data cleared successfully") + + case "clear-cookies": + clearCookiesCmd.Parse(os.Args[2:]) + + // Create a client + c := client.NewClient(*clearCookiesHost, *clearCookiesPort) + + // Clear cookies + err := c.ClearCookies(*clearCookiesTabID, *clearCookiesTimeout) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("Cookies cleared successfully") + + case "clear-storage": + clearStorageCmd.Parse(os.Args[2:]) + + // Create a client + c := client.NewClient(*clearStorageHost, *clearStoragePort) + + // Clear storage + err := c.ClearStorage(*clearStorageTabID, *clearStorageTimeout) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("Storage cleared successfully") + default: printUsage() os.Exit(1) @@ -515,6 +647,12 @@ func printUsage() { fmt.Println(" switch-main Switch back to main page context") fmt.Println(" screenshot Take a screenshot of the current page") fmt.Println(" list-tabs List all open tabs") + fmt.Println(" disable-cache Disable browser cache for a tab") + fmt.Println(" enable-cache Enable browser cache for a tab") + fmt.Println(" clear-cache Clear browser cache for a tab") + fmt.Println(" clear-all-site-data Clear all site data (cookies, storage, cache, etc.)") + fmt.Println(" clear-cookies Clear cookies for a tab") + fmt.Println(" clear-storage Clear web storage (localStorage, sessionStorage, etc.)") fmt.Println(" status Check if the daemon is running") fmt.Println("\nRun 'cremote -h' for more information on a command") fmt.Println("\nBefore using this tool, make sure the daemon is running:") diff --git a/mcp/LLM_USAGE_GUIDE.md b/mcp/LLM_USAGE_GUIDE.md index d84ae07..2ebd23f 100644 --- a/mcp/LLM_USAGE_GUIDE.md +++ b/mcp/LLM_USAGE_GUIDE.md @@ -920,6 +920,120 @@ Returns file management results: } ``` +### 28. `web_disable_cache_cremotemcp` *(New in Phase 6)* +Disable browser cache for a tab. + +**Parameters:** +- `tab` (optional): Tab ID (uses current tab if not specified) +- `timeout` (optional): Timeout in seconds (default: 5) + +**Example Usage:** +``` +# Disable cache for current tab +web_disable_cache_cremotemcp: + timeout: 10 + +# Disable cache for specific tab +web_disable_cache_cremotemcp: + tab: "tab-123" + timeout: 5 +``` + +### 29. `web_enable_cache_cremotemcp` *(New in Phase 6)* +Enable browser cache for a tab. + +**Parameters:** +- `tab` (optional): Tab ID (uses current tab if not specified) +- `timeout` (optional): Timeout in seconds (default: 5) + +**Example Usage:** +``` +# Enable cache for current tab +web_enable_cache_cremotemcp: + timeout: 10 + +# Enable cache for specific tab +web_enable_cache_cremotemcp: + tab: "tab-123" + timeout: 5 +``` + +### 30. `web_clear_cache_cremotemcp` *(New in Phase 6)* +Clear browser cache for a tab. + +**Parameters:** +- `tab` (optional): Tab ID (uses current tab if not specified) +- `timeout` (optional): Timeout in seconds (default: 5) + +**Example Usage:** +``` +# Clear cache for current tab +web_clear_cache_cremotemcp: + timeout: 10 + +# Clear cache for specific tab +web_clear_cache_cremotemcp: + tab: "tab-123" + timeout: 5 +``` + +### 31. `web_clear_all_site_data_cremotemcp` *(New in Phase 6)* +Clear all site data including cookies, storage, cache, etc. for a tab. + +**Parameters:** +- `tab` (optional): Tab ID (uses current tab if not specified) +- `timeout` (optional): Timeout in seconds (default: 5) + +**Example Usage:** +``` +# Clear all site data for current tab +web_clear_all_site_data_cremotemcp: + timeout: 15 + +# Clear all site data for specific tab +web_clear_all_site_data_cremotemcp: + tab: "tab-123" + timeout: 10 +``` + +### 32. `web_clear_cookies_cremotemcp` *(New in Phase 6)* +Clear cookies for a tab. + +**Parameters:** +- `tab` (optional): Tab ID (uses current tab if not specified) +- `timeout` (optional): Timeout in seconds (default: 5) + +**Example Usage:** +``` +# Clear cookies for current tab +web_clear_cookies_cremotemcp: + timeout: 10 + +# Clear cookies for specific tab +web_clear_cookies_cremotemcp: + tab: "tab-123" + timeout: 5 +``` + +### 33. `web_clear_storage_cremotemcp` *(New in Phase 6)* +Clear web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab. + +**Parameters:** +- `tab` (optional): Tab ID (uses current tab if not specified) +- `timeout` (optional): Timeout in seconds (default: 5) + +**Example Usage:** +``` +# Clear storage for current tab +web_clear_storage_cremotemcp: + timeout: 10 + +# Clear storage for specific tab +web_clear_storage_cremotemcp: + tab: "tab-123" + timeout: 5 +``` + ## Common Usage Patterns ### 1. Basic Web Navigation diff --git a/mcp/README.md b/mcp/README.md index 33b23eb..5259bea 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -533,9 +533,87 @@ Manage files (cleanup, list, get info). Operations: `cleanup` (remove old files), `list` (list files), `info` (get file details). +#### 28. `web_disable_cache_cremotemcp` +Disable browser cache for a tab. + +```json +{ + "name": "web_disable_cache_cremotemcp", + "arguments": { + "tab": "tab-123", + "timeout": 5 + } +} +``` + +#### 29. `web_enable_cache_cremotemcp` +Enable browser cache for a tab. + +```json +{ + "name": "web_enable_cache_cremotemcp", + "arguments": { + "tab": "tab-123", + "timeout": 5 + } +} +``` + +#### 30. `web_clear_cache_cremotemcp` +Clear browser cache for a tab. + +```json +{ + "name": "web_clear_cache_cremotemcp", + "arguments": { + "tab": "tab-123", + "timeout": 5 + } +} +``` + +#### 31. `web_clear_all_site_data_cremotemcp` +Clear all site data including cookies, storage, cache, etc. for a tab. + +```json +{ + "name": "web_clear_all_site_data_cremotemcp", + "arguments": { + "tab": "tab-123", + "timeout": 10 + } +} +``` + +#### 32. `web_clear_cookies_cremotemcp` +Clear cookies for a tab. + +```json +{ + "name": "web_clear_cookies_cremotemcp", + "arguments": { + "tab": "tab-123", + "timeout": 5 + } +} +``` + +#### 33. `web_clear_storage_cremotemcp` +Clear web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab. + +```json +{ + "name": "web_clear_storage_cremotemcp", + "arguments": { + "tab": "tab-123", + "timeout": 5 + } +} +``` + ## ๐ŸŽ‰ Complete Enhancement Summary -All 5 phases of the MCP enhancement plan have been successfully implemented, delivering a comprehensive web automation platform with **27 tools** organized across the following capabilities: +All 6 phases of the MCP enhancement plan have been successfully implemented, delivering a comprehensive web automation platform with **33 tools** organized across the following capabilities: ### โœ… Phase 1: Element State and Checking (2 tools) **Enables conditional logic without timing issues** @@ -579,6 +657,17 @@ All 5 phases of the MCP enhancement plan have been successfully implemented, del **Benefits**: Better debugging with targeted screenshots, improved file handling workflows, automatic resource management, enhanced visual debugging capabilities. +### โœ… Phase 6: Browser Cache and Site Data Management (6 tools) +**Enables comprehensive cache and site data control for testing and privacy** +- `web_disable_cache_cremotemcp`: Disable browser cache for a tab +- `web_enable_cache_cremotemcp`: Enable browser cache for a tab +- `web_clear_cache_cremotemcp`: Clear browser cache for a tab +- `web_clear_all_site_data_cremotemcp`: Clear all site data (cookies, storage, cache, etc.) +- `web_clear_cookies_cremotemcp`: Clear cookies for a tab +- `web_clear_storage_cremotemcp`: Clear web storage (localStorage, sessionStorage, IndexedDB, etc.) + +**Benefits**: Essential for testing scenarios requiring fresh page loads, performance testing without cached resources, debugging cache-related issues, ensuring consistent test environments, privacy testing, authentication testing, and complete site data cleanup. + ## Key Benefits for LLM Agents ### ๐Ÿš€ **Efficiency Gains** diff --git a/mcp/main.go b/mcp/main.go index e321453..88d4ba4 100644 --- a/mcp/main.go +++ b/mcp/main.go @@ -2168,6 +2168,278 @@ func main() { }, nil }) + // Cache management tools + mcpServer.AddTool(mcp.Tool{ + Name: "web_disable_cache_cremotemcp", + Description: "Disable browser cache for a tab", + InputSchema: mcp.ToolInputSchema{ + Type: "object", + Properties: map[string]any{ + "tab": map[string]any{ + "type": "string", + "description": "Tab ID (optional, uses current tab)", + }, + "timeout": map[string]any{ + "type": "integer", + "description": "Timeout in seconds (default: 5)", + "default": 5, + }, + }, + }, + }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Convert arguments to map + params, ok := request.Params.Arguments.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid arguments format") + } + + tab := getStringParam(params, "tab", "") + timeout := getIntParam(params, "timeout", 5) + + err := cremoteServer.client.DisableCache(tab, timeout) + if err != nil { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("Error disabling cache: %v", err)), + }, + IsError: true, + }, nil + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent("Cache disabled successfully"), + }, + IsError: false, + }, nil + }) + + mcpServer.AddTool(mcp.Tool{ + Name: "web_enable_cache_cremotemcp", + Description: "Enable browser cache for a tab", + InputSchema: mcp.ToolInputSchema{ + Type: "object", + Properties: map[string]any{ + "tab": map[string]any{ + "type": "string", + "description": "Tab ID (optional, uses current tab)", + }, + "timeout": map[string]any{ + "type": "integer", + "description": "Timeout in seconds (default: 5)", + "default": 5, + }, + }, + }, + }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Convert arguments to map + params, ok := request.Params.Arguments.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid arguments format") + } + + tab := getStringParam(params, "tab", "") + timeout := getIntParam(params, "timeout", 5) + + err := cremoteServer.client.EnableCache(tab, timeout) + if err != nil { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("Error enabling cache: %v", err)), + }, + IsError: true, + }, nil + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent("Cache enabled successfully"), + }, + IsError: false, + }, nil + }) + + mcpServer.AddTool(mcp.Tool{ + Name: "web_clear_cache_cremotemcp", + Description: "Clear browser cache for a tab", + InputSchema: mcp.ToolInputSchema{ + Type: "object", + Properties: map[string]any{ + "tab": map[string]any{ + "type": "string", + "description": "Tab ID (optional, uses current tab)", + }, + "timeout": map[string]any{ + "type": "integer", + "description": "Timeout in seconds (default: 5)", + "default": 5, + }, + }, + }, + }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Convert arguments to map + params, ok := request.Params.Arguments.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid arguments format") + } + + tab := getStringParam(params, "tab", "") + timeout := getIntParam(params, "timeout", 5) + + err := cremoteServer.client.ClearCache(tab, timeout) + if err != nil { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("Error clearing cache: %v", err)), + }, + IsError: true, + }, nil + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent("Cache cleared successfully"), + }, + IsError: false, + }, nil + }) + + // Site data management tools + mcpServer.AddTool(mcp.Tool{ + Name: "web_clear_all_site_data_cremotemcp", + Description: "Clear all site data including cookies, storage, cache, etc. for a tab", + InputSchema: mcp.ToolInputSchema{ + Type: "object", + Properties: map[string]any{ + "tab": map[string]any{ + "type": "string", + "description": "Tab ID (optional, uses current tab)", + }, + "timeout": map[string]any{ + "type": "integer", + "description": "Timeout in seconds (default: 5)", + "default": 5, + }, + }, + }, + }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Convert arguments to map + params, ok := request.Params.Arguments.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid arguments format") + } + + tab := getStringParam(params, "tab", "") + timeout := getIntParam(params, "timeout", 5) + + err := cremoteServer.client.ClearAllSiteData(tab, timeout) + if err != nil { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("Error clearing all site data: %v", err)), + }, + IsError: true, + }, nil + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent("All site data cleared successfully"), + }, + IsError: false, + }, nil + }) + + mcpServer.AddTool(mcp.Tool{ + Name: "web_clear_cookies_cremotemcp", + Description: "Clear cookies for a tab", + InputSchema: mcp.ToolInputSchema{ + Type: "object", + Properties: map[string]any{ + "tab": map[string]any{ + "type": "string", + "description": "Tab ID (optional, uses current tab)", + }, + "timeout": map[string]any{ + "type": "integer", + "description": "Timeout in seconds (default: 5)", + "default": 5, + }, + }, + }, + }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Convert arguments to map + params, ok := request.Params.Arguments.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid arguments format") + } + + tab := getStringParam(params, "tab", "") + timeout := getIntParam(params, "timeout", 5) + + err := cremoteServer.client.ClearCookies(tab, timeout) + if err != nil { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("Error clearing cookies: %v", err)), + }, + IsError: true, + }, nil + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent("Cookies cleared successfully"), + }, + IsError: false, + }, nil + }) + + mcpServer.AddTool(mcp.Tool{ + Name: "web_clear_storage_cremotemcp", + Description: "Clear web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab", + InputSchema: mcp.ToolInputSchema{ + Type: "object", + Properties: map[string]any{ + "tab": map[string]any{ + "type": "string", + "description": "Tab ID (optional, uses current tab)", + }, + "timeout": map[string]any{ + "type": "integer", + "description": "Timeout in seconds (default: 5)", + "default": 5, + }, + }, + }, + }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Convert arguments to map + params, ok := request.Params.Arguments.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid arguments format") + } + + tab := getStringParam(params, "tab", "") + timeout := getIntParam(params, "timeout", 5) + + err := cremoteServer.client.ClearStorage(tab, timeout) + if err != nil { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent(fmt.Sprintf("Error clearing storage: %v", err)), + }, + IsError: true, + }, nil + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + mcp.NewTextContent("Storage cleared successfully"), + }, + IsError: false, + }, nil + }) + // Start the server log.Printf("Cremote MCP server ready") if err := server.ServeStdio(mcpServer); err != nil { diff --git a/mcp_cache_test_example.md b/mcp_cache_test_example.md new file mode 100644 index 0000000..59f1be7 --- /dev/null +++ b/mcp_cache_test_example.md @@ -0,0 +1,199 @@ +# MCP Cache and Site Data Management Test Examples + +This document provides examples of how to use the new cache and site data management tools through the MCP interface. + +## Prerequisites + +1. Ensure cremotedaemon is running +2. Ensure Chrome/Chromium is running with remote debugging enabled +3. Configure your MCP client to use the cremote MCP server + +## Example 1: Basic Cache Disable/Enable Workflow + +```yaml +# Navigate to a test page +web_navigate_cremotemcp: + url: "https://httpbin.org/cache/60" + screenshot: true + +# Disable cache for testing +web_disable_cache_cremotemcp: + timeout: 10 + +# Reload page to test without cache +web_navigate_cremotemcp: + url: "https://httpbin.org/cache/60" + +# Re-enable cache +web_enable_cache_cremotemcp: + timeout: 10 +``` + +## Example 2: Performance Testing Workflow + +```yaml +# Clear cache before performance test +web_clear_cache_cremotemcp: + timeout: 15 + +# Disable cache for cold load test +web_disable_cache_cremotemcp: + timeout: 10 + +# Navigate and measure performance +web_navigate_cremotemcp: + url: "https://example.com" + screenshot: true + +# Get performance metrics +web_performance_metrics_cremotemcp: {} + +# Enable cache for warm load test +web_enable_cache_cremotemcp: + timeout: 10 + +# Navigate again and compare +web_navigate_cremotemcp: + url: "https://example.com" + +# Get performance metrics again +web_performance_metrics_cremotemcp: {} +``` + +## Example 3: Multi-Tab Cache Management + +```yaml +# Open first tab and disable cache +web_manage_tabs_cremotemcp: + action: "open" + +web_disable_cache_cremotemcp: + tab: "tab-1" + timeout: 10 + +# Open second tab and keep cache enabled +web_manage_tabs_cremotemcp: + action: "open" + +# Navigate both tabs to same URL +web_navigate_cremotemcp: + url: "https://httpbin.org/cache/60" + tab: "tab-1" + +web_navigate_cremotemcp: + url: "https://httpbin.org/cache/60" + tab: "tab-2" +``` + +## Example 4: Development Workflow + +```yaml +# Disable cache for development +web_disable_cache_cremotemcp: + timeout: 10 + +# Navigate to development site +web_navigate_cremotemcp: + url: "http://localhost:3000" + +# Interact with page +web_interact_cremotemcp: + action: "click" + selector: "#refresh-button" + +# Take screenshot for documentation +web_screenshot_enhanced_cremotemcp: + output: "/tmp/dev-screenshot.png" + full_page: true +``` + +## Error Handling + +The cache management tools will return appropriate error messages if: +- The daemon is not running +- Chrome/Chromium is not accessible +- The specified tab doesn't exist +- Network operations timeout + +Example error response: +```json +{ + "error": "failed to disable cache: tab not found: tab-123", + "isError": true +} +``` + +## Best Practices + +1. **Always set appropriate timeouts** for cache operations (5-15 seconds recommended) +2. **Clear cache before performance tests** to ensure consistent baseline measurements +3. **Disable cache during development** to see changes immediately +4. **Re-enable cache after testing** to restore normal browsing behavior +5. **Use specific tab IDs** when working with multiple tabs to avoid confusion + +## Example 5: Authentication Testing Workflow + +```yaml +# Clear all site data before testing login +web_clear_all_site_data_cremotemcp: + timeout: 15 + +# Navigate to login page +web_navigate_cremotemcp: + url: "https://example.com/login" + +# Fill login form +web_form_fill_bulk_cremotemcp: + fields: + username: "testuser" + password: "testpass" + +# Submit and verify login +web_interact_cremotemcp: + action: "click" + selector: "#login-button" + +# Clear cookies to test logout behavior +web_clear_cookies_cremotemcp: + timeout: 10 + +# Refresh page to verify logout +web_navigate_cremotemcp: + url: "https://example.com/dashboard" +``` + +## Example 6: Storage Testing Workflow + +```yaml +# Clear storage before testing +web_clear_storage_cremotemcp: + timeout: 10 + +# Navigate to application +web_navigate_cremotemcp: + url: "https://example.com/app" + +# Interact with app to create storage data +web_interact_cremotemcp: + action: "click" + selector: "#save-data-button" + +# Clear storage to test data persistence +web_clear_storage_cremotemcp: + timeout: 10 + +# Refresh to test if data is gone +web_navigate_cremotemcp: + url: "https://example.com/app" +``` + +## Integration with Other Tools + +Cache and site data management works seamlessly with other cremote MCP tools: + +- Use with `web_performance_metrics_cremotemcp` for performance testing +- Combine with `web_navigate_cremotemcp` for controlled page loading +- Use with `web_screenshot_enhanced_cremotemcp` for visual testing +- Integrate with `web_interact_cremotemcp` for user interaction testing +- Use with `web_form_fill_bulk_cremotemcp` for authentication testing +- Combine with `web_extract_cremotemcp` tools for data verification after clearing diff --git a/test_cache_management.sh b/test_cache_management.sh new file mode 100755 index 0000000..f675894 --- /dev/null +++ b/test_cache_management.sh @@ -0,0 +1,129 @@ +#!/bin/bash + +# Test script for cache management functionality +# This script demonstrates the new cache management features in cremote + +echo "๐Ÿงช Testing Cremote Cache Management Features" +echo "=============================================" + +# Check if daemon is running +echo "๐Ÿ“ก Checking daemon status..." +if ! ./cremote status > /dev/null 2>&1; then + echo "โŒ Daemon is not running. Please start it with: cremotedaemon" + exit 1 +fi +echo "โœ… Daemon is running" + +# Open a new tab +echo "๐ŸŒ Opening new tab..." +TAB_ID=$(./cremote open-tab) +if [ $? -ne 0 ]; then + echo "โŒ Failed to open tab" + exit 1 +fi +echo "โœ… Tab opened: $TAB_ID" + +# Load a test page +echo "๐Ÿ“„ Loading test page..." +./cremote load-url --url="https://httpbin.org/cache/60" --timeout=10 +if [ $? -ne 0 ]; then + echo "โŒ Failed to load page" + exit 1 +fi +echo "โœ… Page loaded" + +# Test 1: Disable cache +echo "" +echo "๐Ÿšซ Test 1: Disabling cache..." +./cremote disable-cache --timeout=10 +if [ $? -eq 0 ]; then + echo "โœ… Cache disabled successfully" +else + echo "โŒ Failed to disable cache" +fi + +# Test 2: Enable cache +echo "" +echo "โœ… Test 2: Enabling cache..." +./cremote enable-cache --timeout=10 +if [ $? -eq 0 ]; then + echo "โœ… Cache enabled successfully" +else + echo "โŒ Failed to enable cache" +fi + +# Test 3: Clear cache +echo "" +echo "๐Ÿงน Test 3: Clearing cache..." +./cremote clear-cache --timeout=10 +if [ $? -eq 0 ]; then + echo "โœ… Cache cleared successfully" +else + echo "โŒ Failed to clear cache" +fi + +# Test 4: Test with specific tab ID +echo "" +echo "๐ŸŽฏ Test 4: Testing with specific tab ID..." +./cremote disable-cache --tab="$TAB_ID" --timeout=10 +if [ $? -eq 0 ]; then + echo "โœ… Cache disabled for specific tab successfully" +else + echo "โŒ Failed to disable cache for specific tab" +fi + +# Clean up +echo "" +echo "๐Ÿงน Cleaning up..." +./cremote close-tab --tab="$TAB_ID" +echo "โœ… Tab closed" + +echo "" +# Test 5: Clear all site data +echo "" +echo "๐Ÿงน Test 5: Clearing all site data..." +./cremote clear-all-site-data --timeout=15 +if [ $? -eq 0 ]; then + echo "โœ… All site data cleared successfully" +else + echo "โŒ Failed to clear all site data" +fi + +# Test 6: Clear cookies only +echo "" +echo "๐Ÿช Test 6: Clearing cookies..." +./cremote clear-cookies --timeout=10 +if [ $? -eq 0 ]; then + echo "โœ… Cookies cleared successfully" +else + echo "โŒ Failed to clear cookies" +fi + +# Test 7: Clear storage only +echo "" +echo "๐Ÿ’พ Test 7: Clearing storage..." +./cremote clear-storage --timeout=10 +if [ $? -eq 0 ]; then + echo "โœ… Storage cleared successfully" +else + echo "โŒ Failed to clear storage" +fi + +echo "๐ŸŽ‰ Cache and site data management tests completed!" +echo "" +echo "๐Ÿ“‹ Summary of new features:" +echo " โ€ข disable-cache: Disables browser cache for testing" +echo " โ€ข enable-cache: Re-enables browser cache" +echo " โ€ข clear-cache: Clears existing cached resources" +echo " โ€ข clear-all-site-data: Clears ALL site data (cookies, storage, cache, etc.)" +echo " โ€ข clear-cookies: Clears cookies only" +echo " โ€ข clear-storage: Clears web storage only (localStorage, sessionStorage, etc.)" +echo "" +echo "๐Ÿ’ก Use cases:" +echo " โ€ข Testing: Ensure fresh page loads without cache" +echo " โ€ข Performance: Test cold load performance" +echo " โ€ข Debugging: Resolve cache-related issues" +echo " โ€ข Development: See changes immediately" +echo " โ€ข Authentication: Clear cookies to test login/logout flows" +echo " โ€ข Privacy: Clear all site data for clean state testing" +echo " โ€ข Storage: Clear web storage to test application state"