fastled 1.3.38__py3-none-any.whl → 1.3.39__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastled/__version__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # IMPORTANT! There's a bug in github which will REJECT any version update
2
2
  # that has any other change in the repo. Please bump the version as the
3
3
  # ONLY change in a commit, or else the pypi update and the release will fail.
4
- __version__ = "1.3.38"
4
+ __version__ = "1.3.39"
5
5
 
6
6
  __version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
fastled/args.py CHANGED
@@ -23,6 +23,8 @@ class Args:
23
23
  quick: bool
24
24
  release: bool
25
25
  ram_disk_size: str # suffixed liked "25mb" or "1gb"
26
+ playwright: bool = False # Use Playwright browser instead of system default
27
+ no_playwright_auto_resize: bool = False # Disable Playwright auto-resize
26
28
  clear = False # Force the last running container to be removed. Useful for benchmarking.
27
29
 
28
30
  @staticmethod
@@ -66,6 +68,12 @@ class Args:
66
68
  assert isinstance(
67
69
  args.release, bool
68
70
  ), f"expected bool, got {type(args.release)}"
71
+ assert isinstance(
72
+ args.playwright, bool
73
+ ), f"expected bool, got {type(args.playwright)}"
74
+ assert isinstance(
75
+ args.no_playwright_auto_resize, bool
76
+ ), f"expected bool, got {type(args.no_playwright_auto_resize)}"
69
77
 
70
78
  init: bool | str = False
71
79
  if args.init is None:
@@ -93,4 +101,6 @@ class Args:
93
101
  quick=args.quick,
94
102
  release=args.release,
95
103
  ram_disk_size=args.ram_disk_size,
104
+ playwright=args.playwright,
105
+ no_playwright_auto_resize=args.no_playwright_auto_resize,
96
106
  )
fastled/client_server.py CHANGED
@@ -258,6 +258,8 @@ def run_client(
258
258
  ) = None, # None means auto select a free port, http_port < 0 means no server.
259
259
  clear: bool = False,
260
260
  no_platformio: bool = False,
261
+ use_playwright: bool = False,
262
+ playwright_auto_resize: bool = True,
261
263
  ) -> int:
262
264
  has_checked_newer_version_yet = False
263
265
  compile_server: CompileServer | None = None
@@ -338,6 +340,8 @@ def run_client(
338
340
  port=http_port,
339
341
  compile_server_port=port,
340
342
  open_browser=open_web_browser,
343
+ use_playwright=use_playwright,
344
+ playwright_auto_resize=playwright_auto_resize,
341
345
  )
342
346
  else:
343
347
  print("\nCompilation successful.")
@@ -560,6 +564,8 @@ def run_client_server(args: Args) -> int:
560
564
  profile=profile,
561
565
  clear=args.clear,
562
566
  no_platformio=no_platformio,
567
+ use_playwright=args.playwright,
568
+ playwright_auto_resize=not args.no_playwright_auto_resize,
563
569
  )
564
570
  except KeyboardInterrupt:
565
571
  return 1
fastled/open_browser.py CHANGED
@@ -8,6 +8,29 @@ from pathlib import Path
8
8
 
9
9
  from fastled.server_flask import run_flask_in_thread
10
10
 
11
+ try:
12
+ from fastled.playwright_browser import is_playwright_available, open_with_playwright
13
+
14
+ PLAYWRIGHT_AVAILABLE = is_playwright_available()
15
+ except ImportError:
16
+ PLAYWRIGHT_AVAILABLE = False
17
+ open_with_playwright = None
18
+
19
+ # Global reference to keep Playwright browser alive
20
+ _playwright_browser_proxy = None
21
+
22
+
23
+ def cleanup_playwright_browser() -> None:
24
+ """Clean up the Playwright browser on exit."""
25
+ global _playwright_browser_proxy
26
+ if _playwright_browser_proxy:
27
+ _playwright_browser_proxy.close()
28
+ _playwright_browser_proxy = None
29
+
30
+
31
+ # Register cleanup function
32
+ atexit.register(cleanup_playwright_browser)
33
+
11
34
  DEFAULT_PORT = 8089 # different than live version.
12
35
  PYTHON_EXE = sys.executable
13
36
 
@@ -78,6 +101,8 @@ def spawn_http_server(
78
101
  compile_server_port: int,
79
102
  port: int | None = None,
80
103
  open_browser: bool = True,
104
+ use_playwright: bool = False,
105
+ playwright_auto_resize: bool = True,
81
106
  ) -> Process:
82
107
 
83
108
  if port is not None and not is_port_free(port):
@@ -105,14 +130,38 @@ def spawn_http_server(
105
130
 
106
131
  wait_for_server(port)
107
132
  if open_browser:
108
- print(f"Opening browser to http://localhost:{port}")
109
- import webbrowser
110
-
111
- webbrowser.open(
112
- url=f"http://localhost:{port}",
113
- new=1,
114
- autoraise=True,
115
- )
133
+ url = f"http://localhost:{port}"
134
+ if use_playwright and PLAYWRIGHT_AVAILABLE and open_with_playwright is not None:
135
+ print(f"Opening FastLED sketch in Playwright browser: {url}")
136
+ if playwright_auto_resize:
137
+ print(
138
+ "Auto-resize enabled: Browser window will automatically adjust to content size"
139
+ )
140
+ global _playwright_browser_proxy
141
+ _playwright_browser_proxy = open_with_playwright(
142
+ url, auto_resize=playwright_auto_resize
143
+ )
144
+ elif use_playwright and not PLAYWRIGHT_AVAILABLE:
145
+ print(
146
+ "Playwright requested but not available. Install with: pip install fastled[full]"
147
+ )
148
+ print(f"Falling back to system browser: {url}")
149
+ import webbrowser
150
+
151
+ webbrowser.open(
152
+ url=url,
153
+ new=1,
154
+ autoraise=True,
155
+ )
156
+ else:
157
+ print(f"Opening browser to {url}")
158
+ import webbrowser
159
+
160
+ webbrowser.open(
161
+ url=url,
162
+ new=1,
163
+ autoraise=True,
164
+ )
116
165
  return proc
117
166
 
118
167
 
fastled/parse_args.py CHANGED
@@ -35,12 +35,15 @@ FastLED WASM Compiler - Useful options:
35
35
  --profile Enable profiling the C++ build system
36
36
  --update Update the docker image for the wasm compiler
37
37
  --purge Remove all FastLED containers and images
38
+ --playwright Use Playwright browser (requires 'pip install fastled[full]')
39
+ --no-playwright-auto-resize Disable automatic window resizing in Playwright
38
40
  --version Show version information
39
41
  --help Show detailed help
40
42
  Examples:
41
43
  fastled (will auto detect the sketch directory and prompt you)
42
44
  fastled my_sketch
43
45
  fastled my_sketch --web (compiles using the web compiler only)
46
+ fastled my_sketch --playwright (opens in Playwright browser)
44
47
  fastled --init Blink (initializes a new sketch directory with the Blink example)
45
48
  fastled --server (runs the compiler server in the current directory)
46
49
 
@@ -158,6 +161,18 @@ def parse_args() -> Args:
158
161
  help="Remove all FastLED containers and images",
159
162
  )
160
163
 
164
+ parser.add_argument(
165
+ "--playwright",
166
+ action="store_true",
167
+ help="Use Playwright browser instead of system default (requires 'pip install fastled[full]')",
168
+ )
169
+
170
+ parser.add_argument(
171
+ "--no-playwright-auto-resize",
172
+ action="store_true",
173
+ help="Disable automatic window resizing in Playwright browser",
174
+ )
175
+
161
176
  build_mode = parser.add_mutually_exclusive_group()
162
177
  build_mode.add_argument("--debug", action="store_true", help="Build in debug mode")
163
178
  build_mode.add_argument(
@@ -0,0 +1,468 @@
1
+ """
2
+ Playwright browser integration for FastLED WASM compiler.
3
+
4
+ This module provides functionality to open the compiled FastLED sketch
5
+ in a Playwright browser instead of the default system browser when
6
+ the 'full' optional dependency is installed.
7
+ """
8
+
9
+ import asyncio
10
+ import sys
11
+ import warnings
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ if TYPE_CHECKING:
15
+ from playwright.async_api import Browser, Page
16
+
17
+ try:
18
+ from playwright.async_api import Browser, Page, async_playwright
19
+
20
+ PLAYWRIGHT_AVAILABLE = True
21
+ except ImportError:
22
+ PLAYWRIGHT_AVAILABLE = False
23
+ async_playwright = None
24
+ Browser = Any # type: ignore
25
+ Page = Any # type: ignore
26
+
27
+
28
+ def is_playwright_available() -> bool:
29
+ """Check if Playwright is available."""
30
+ return PLAYWRIGHT_AVAILABLE
31
+
32
+
33
+ class PlaywrightBrowser:
34
+ """Playwright browser manager for FastLED sketches."""
35
+
36
+ def __init__(self, headless: bool = False, auto_resize: bool = True):
37
+ """Initialize the Playwright browser manager.
38
+
39
+ Args:
40
+ headless: Whether to run the browser in headless mode
41
+ auto_resize: Whether to automatically resize the browser window to fit content
42
+ """
43
+ if not PLAYWRIGHT_AVAILABLE:
44
+ raise ImportError(
45
+ "Playwright is not installed. Install with: pip install fastled[full]"
46
+ )
47
+
48
+ # debug
49
+ auto_resize = True
50
+
51
+ self.headless = headless
52
+ self.auto_resize = auto_resize
53
+ self.browser: Any = None
54
+ self.page: Any = None
55
+ self.playwright: Any = None
56
+
57
+ async def start(self) -> None:
58
+ """Start the Playwright browser."""
59
+ if self.playwright is None and async_playwright is not None:
60
+ self.playwright = await async_playwright().start()
61
+
62
+ if self.browser is None and self.playwright is not None:
63
+ # Try Chrome first, then Firefox, then WebKit
64
+ try:
65
+ self.browser = await self.playwright.chromium.launch(
66
+ headless=self.headless,
67
+ args=["--disable-web-security", "--allow-running-insecure-content"],
68
+ )
69
+ except Exception:
70
+ try:
71
+ self.browser = await self.playwright.firefox.launch(
72
+ headless=self.headless
73
+ )
74
+ except Exception:
75
+ self.browser = await self.playwright.webkit.launch(
76
+ headless=self.headless
77
+ )
78
+
79
+ if self.page is None and self.browser is not None:
80
+ # Create a new browser context and page
81
+ context = await self.browser.new_context()
82
+ self.page = await context.new_page()
83
+
84
+ async def open_url(self, url: str) -> None:
85
+ """Open a URL in the Playwright browser.
86
+
87
+ Args:
88
+ url: The URL to open
89
+ """
90
+ if self.page is None:
91
+ await self.start()
92
+
93
+ print(f"Opening FastLED sketch in Playwright browser: {url}")
94
+ if self.page is not None:
95
+ await self.page.goto(url)
96
+
97
+ # Wait for the page to load
98
+ await self.page.wait_for_load_state("networkidle")
99
+
100
+ # Set up auto-resizing functionality if enabled
101
+ if self.auto_resize:
102
+ await self._setup_auto_resize()
103
+
104
+ async def _setup_auto_resize(self) -> None:
105
+ """Set up automatic window resizing based on content size."""
106
+ if self.page is None:
107
+ print("[PYTHON] Cannot setup auto-resize: page is None")
108
+ return
109
+
110
+ print(
111
+ "[PYTHON] Setting up browser window tracking with viewport-only adjustment"
112
+ )
113
+
114
+ # Start polling loop that tracks browser window changes and adjusts viewport only
115
+ asyncio.create_task(self._track_browser_adjust_viewport())
116
+
117
+ async def _get_window_info(self) -> dict[str, int] | None:
118
+ """Get browser window dimensions information.
119
+
120
+ Returns:
121
+ Dictionary containing window dimensions or None if unable to retrieve
122
+ """
123
+ if self.page is None:
124
+ return None
125
+
126
+ try:
127
+ return await self.page.evaluate(
128
+ """
129
+ () => {
130
+ return {
131
+ outerWidth: window.outerWidth,
132
+ outerHeight: window.outerHeight,
133
+ innerWidth: window.innerWidth,
134
+ innerHeight: window.innerHeight,
135
+ contentWidth: document.documentElement.clientWidth,
136
+ contentHeight: document.documentElement.clientHeight
137
+ };
138
+ }
139
+ """
140
+ )
141
+ except Exception:
142
+ return None
143
+
144
+ async def _track_browser_adjust_viewport(self) -> None:
145
+ """Track browser window outer size changes and adjust viewport accordingly."""
146
+ if self.page is None:
147
+ return
148
+
149
+ print(
150
+ "[PYTHON] Starting browser window tracking (outer size → viewport adjustment)"
151
+ )
152
+ last_outer_size = None
153
+
154
+ while True:
155
+ try:
156
+ # Wait 1 second between polls
157
+ await asyncio.sleep(1)
158
+
159
+ # Check if page is still alive
160
+ if self.page is None or self.page.is_closed():
161
+ print("[PYTHON] Page closed, stopping browser tracking")
162
+ break
163
+
164
+ # Get browser window dimensions
165
+ window_info = await self._get_window_info()
166
+
167
+ if window_info:
168
+ current_outer = (
169
+ window_info["outerWidth"],
170
+ window_info["outerHeight"],
171
+ )
172
+
173
+ # Print current state occasionally
174
+ if last_outer_size is None or current_outer != last_outer_size:
175
+ print(
176
+ f"[PYTHON] Browser: outer={window_info['outerWidth']}x{window_info['outerHeight']}, content={window_info['contentWidth']}x{window_info['contentHeight']}"
177
+ )
178
+
179
+ # Track changes in OUTER window size (user resizes browser)
180
+ if last_outer_size is None or current_outer != last_outer_size:
181
+
182
+ if last_outer_size is not None:
183
+ print("[PYTHON] *** BROWSER WINDOW RESIZED ***")
184
+ print(
185
+ f"[PYTHON] Outer window changed from {last_outer_size[0]}x{last_outer_size[1]} to {current_outer[0]}x{current_outer[1]}"
186
+ )
187
+
188
+ last_outer_size = current_outer
189
+
190
+ # Set viewport to match the outer window size
191
+ if not self.headless:
192
+ try:
193
+ outer_width = int(window_info["outerWidth"])
194
+ outer_height = int(window_info["outerHeight"])
195
+
196
+ print(
197
+ f"[PYTHON] Setting viewport to match outer window size: {outer_width}x{outer_height}"
198
+ )
199
+
200
+ await self.page.set_viewport_size(
201
+ {"width": outer_width, "height": outer_height}
202
+ )
203
+ print("[PYTHON] Viewport set successfully")
204
+
205
+ # Wait briefly for browser to settle after viewport change
206
+ # await asyncio.sleep(0.5)
207
+
208
+ # Query the actual window dimensions after the viewport change
209
+ updated_window_info = await self._get_window_info()
210
+
211
+ if updated_window_info:
212
+
213
+ # Update our tracking with the actual final outer size
214
+ last_outer_size = (
215
+ updated_window_info["outerWidth"],
216
+ updated_window_info["outerHeight"],
217
+ )
218
+ print(
219
+ f"[PYTHON] Updated last_outer_size to actual final size: {last_outer_size}"
220
+ )
221
+ else:
222
+ print("[PYTHON] Could not get updated window info")
223
+
224
+ except Exception as e:
225
+ print(f"[PYTHON] Failed to set viewport: {e}")
226
+
227
+ else:
228
+ print("[PYTHON] Could not get browser window info")
229
+
230
+ except Exception as e:
231
+ print(f"[PYTHON] Error in browser tracking: {e}")
232
+ continue
233
+
234
+ async def wait_for_close(self) -> None:
235
+ """Wait for the browser to be closed."""
236
+ if self.browser is None:
237
+ return
238
+
239
+ try:
240
+ # Wait for the browser to be closed
241
+ while not self.browser.is_closed():
242
+ await asyncio.sleep(1)
243
+ except Exception:
244
+ pass
245
+
246
+ async def close(self) -> None:
247
+ """Close the Playwright browser."""
248
+ if self.page:
249
+ await self.page.close()
250
+ self.page = None
251
+
252
+ if self.browser:
253
+ await self.browser.close()
254
+ self.browser = None
255
+
256
+ if self.playwright:
257
+ await self.playwright.stop()
258
+ self.playwright = None
259
+
260
+
261
+ def run_playwright_browser(
262
+ url: str, headless: bool = False, auto_resize: bool = True
263
+ ) -> None:
264
+ """Run Playwright browser in a separate process.
265
+
266
+ Args:
267
+ url: The URL to open
268
+ headless: Whether to run in headless mode
269
+ auto_resize: Whether to automatically resize the browser window to fit content
270
+ """
271
+ if not PLAYWRIGHT_AVAILABLE:
272
+ warnings.warn(
273
+ "Playwright is not installed. Install with: pip install fastled[full]. "
274
+ "Falling back to default browser."
275
+ )
276
+ return
277
+
278
+ async def main():
279
+ browser = PlaywrightBrowser(headless=headless, auto_resize=auto_resize)
280
+ try:
281
+ await browser.start()
282
+ await browser.open_url(url)
283
+
284
+ if not headless:
285
+ print("Playwright browser opened. Press Ctrl+C to close.")
286
+ await browser.wait_for_close()
287
+ else:
288
+ # In headless mode, just wait a bit for the page to load
289
+ await asyncio.sleep(2)
290
+
291
+ except KeyboardInterrupt:
292
+ print("\nClosing Playwright browser...")
293
+ finally:
294
+ await browser.close()
295
+
296
+ try:
297
+ asyncio.run(main())
298
+ except KeyboardInterrupt:
299
+ print("\nPlaywright browser closed.")
300
+ except Exception as e:
301
+ warnings.warn(
302
+ f"Playwright browser failed: {e}. Falling back to default browser."
303
+ )
304
+
305
+
306
+ class PlaywrightBrowserProxy:
307
+ """Proxy object to manage Playwright browser lifecycle."""
308
+
309
+ def __init__(self):
310
+ self.process = None
311
+ self.browser_manager = None
312
+
313
+ def open(self, url: str, headless: bool = False, auto_resize: bool = True) -> None:
314
+ """Open URL with Playwright browser and keep it alive.
315
+
316
+ Args:
317
+ url: The URL to open
318
+ headless: Whether to run in headless mode
319
+ auto_resize: Whether to automatically resize the browser window to fit content
320
+ """
321
+ if not PLAYWRIGHT_AVAILABLE:
322
+ warnings.warn(
323
+ "Playwright is not installed. Install with: pip install fastled[full]. "
324
+ "Falling back to default browser."
325
+ )
326
+ # Fall back to default browser
327
+ import webbrowser
328
+
329
+ webbrowser.open(url)
330
+ return
331
+
332
+ try:
333
+ # Run Playwright in a separate process to avoid blocking
334
+ import multiprocessing
335
+
336
+ self.process = multiprocessing.Process(
337
+ target=run_playwright_browser_persistent,
338
+ args=(url, headless, auto_resize),
339
+ )
340
+ self.process.start()
341
+
342
+ # Register cleanup
343
+ import atexit
344
+
345
+ atexit.register(self.close)
346
+
347
+ except Exception as e:
348
+ warnings.warn(
349
+ f"Failed to start Playwright browser: {e}. Falling back to default browser."
350
+ )
351
+ import webbrowser
352
+
353
+ webbrowser.open(url)
354
+
355
+ def close(self) -> None:
356
+ """Close the Playwright browser."""
357
+ if self.process and self.process.is_alive():
358
+ print("Closing Playwright browser...")
359
+ self.process.terminate()
360
+ self.process.join(timeout=5)
361
+ if self.process.is_alive():
362
+ self.process.kill()
363
+ self.process = None
364
+
365
+
366
+ def run_playwright_browser_persistent(
367
+ url: str, headless: bool = False, auto_resize: bool = True
368
+ ) -> None:
369
+ """Run Playwright browser in a persistent mode that stays alive until terminated.
370
+
371
+ Args:
372
+ url: The URL to open
373
+ headless: Whether to run in headless mode
374
+ auto_resize: Whether to automatically resize the browser window to fit content
375
+ """
376
+ if not PLAYWRIGHT_AVAILABLE:
377
+ return
378
+
379
+ async def main():
380
+ browser = PlaywrightBrowser(headless=headless, auto_resize=auto_resize)
381
+ try:
382
+ await browser.start()
383
+ await browser.open_url(url)
384
+
385
+ print(
386
+ "Playwright browser opened. Browser will remain open until the FastLED process exits."
387
+ )
388
+
389
+ # Keep the browser alive indefinitely
390
+ while True:
391
+ await asyncio.sleep(1)
392
+
393
+ except KeyboardInterrupt:
394
+ print("\nClosing Playwright browser...")
395
+ except Exception as e:
396
+ print(f"Playwright browser error: {e}")
397
+ finally:
398
+ await browser.close()
399
+
400
+ try:
401
+ asyncio.run(main())
402
+ except KeyboardInterrupt:
403
+ print("\nPlaywright browser closed.")
404
+ except Exception as e:
405
+ print(f"Playwright browser failed: {e}")
406
+
407
+
408
+ def open_with_playwright(
409
+ url: str, headless: bool = False, auto_resize: bool = True
410
+ ) -> PlaywrightBrowserProxy:
411
+ """Open URL with Playwright browser and return a proxy object for lifecycle management.
412
+
413
+ This function can be used as a drop-in replacement for webbrowser.open().
414
+
415
+ Args:
416
+ url: The URL to open
417
+ headless: Whether to run in headless mode
418
+ auto_resize: Whether to automatically resize the browser window to fit content
419
+
420
+ Returns:
421
+ PlaywrightBrowserProxy object for managing the browser lifecycle
422
+ """
423
+ proxy = PlaywrightBrowserProxy()
424
+ proxy.open(url, headless, auto_resize)
425
+ return proxy
426
+
427
+
428
+ def install_playwright_browsers() -> bool:
429
+ """Install Playwright browsers if not already installed.
430
+
431
+ Returns:
432
+ True if installation was successful or browsers were already installed
433
+ """
434
+ if not PLAYWRIGHT_AVAILABLE:
435
+ return False
436
+
437
+ try:
438
+ from playwright.sync_api import sync_playwright
439
+
440
+ with sync_playwright() as p:
441
+ # Try to launch a browser to see if it's installed
442
+ try:
443
+ browser = p.chromium.launch(headless=True)
444
+ browser.close()
445
+ return True
446
+ except Exception:
447
+ pass
448
+
449
+ # If we get here, browsers need to be installed
450
+ print("Installing Playwright browsers...")
451
+ import subprocess
452
+
453
+ result = subprocess.run(
454
+ [sys.executable, "-m", "playwright", "install", "chromium"],
455
+ capture_output=True,
456
+ text=True,
457
+ )
458
+
459
+ if result.returncode == 0:
460
+ print("Playwright browsers installed successfully.")
461
+ return True
462
+ else:
463
+ print(f"Failed to install Playwright browsers: {result.stderr}")
464
+ return False
465
+
466
+ except Exception as e:
467
+ print(f"Error installing Playwright browsers: {e}")
468
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.3.38
3
+ Version: 1.3.39
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -22,6 +22,8 @@ Requires-Dist: Flask>=3.0.0
22
22
  Requires-Dist: flask-cors>=4.0.0
23
23
  Requires-Dist: livereload
24
24
  Requires-Dist: disklru>=2.0.4
25
+ Provides-Extra: full
26
+ Requires-Dist: playwright>=1.40.0; extra == "full"
25
27
  Dynamic: home-page
26
28
  Dynamic: license-file
27
29
  Dynamic: maintainer
@@ -71,7 +73,33 @@ $ cd mysketchdirectory
71
73
  $ fastled
72
74
  ```
73
75
 
76
+ ## Playwright Browser Support
74
77
 
78
+ For enhanced browser control and automation capabilities, you can install the full version with Playwright support:
79
+
80
+ ```bash
81
+ $ pip install fastled[full]
82
+ ```
83
+
84
+ This enables the `--playwright` flag which opens your compiled sketch in a Playwright-controlled browser instead of your system's default browser:
85
+
86
+ ```bash
87
+ $ fastled my_sketch --playwright
88
+ ```
89
+
90
+ The Playwright browser provides better automation capabilities and is especially useful for:
91
+ - Automated testing of your LED sketches
92
+ - Consistent cross-platform browser behavior
93
+ - Advanced debugging and development workflows
94
+ - Persistent browser sessions that stay open until the FastLED process exits
95
+
96
+ **Key Benefits:**
97
+ - The Playwright browser remains open throughout your development session
98
+ - Automatic cleanup when the FastLED process exits
99
+ - Better control over browser behavior and automation capabilities
100
+ - Consistent behavior across different platforms
101
+
102
+ If Playwright is not installed and you use the `--playwright` flag, the system will gracefully fall back to your default browser.
75
103
 
76
104
  # Install
77
105
 
@@ -81,14 +109,26 @@ This is a python app, so any python package manager will work. We also provide p
81
109
 
82
110
  `pip install fastled`
83
111
 
112
+ ### Pip with Playwright Support
113
+
114
+ `pip install fastled[full]`
115
+
84
116
  ### UV
85
117
 
86
118
  `uv pip install fastled --system`
87
119
 
120
+ ### UV with Playwright Support
121
+
122
+ `uv pip install "fastled[full]" --system`
123
+
88
124
  ### Pipx
89
125
 
90
126
  `pipx install fastled`
91
127
 
128
+ ### Pipx with Playwright Support
129
+
130
+ `pipx install "fastled[full]"`
131
+
92
132
  ### Executables
93
133
 
94
134
  * Windows: https://github.com/zackees/fastled-wasm/releases/latest/download/fastled-windows-x64.zip
@@ -1,12 +1,12 @@
1
1
  fastled/__init__.py,sha256=NBk5Ef65nIe0F_rvBrSGeoyouFOKFeQXqHrTCJQheeI,7201
2
2
  fastled/__main__.py,sha256=OcKv2ER1_iQAsZzLIUb3C8hRC9L2clNOhCrjpshrlf4,336
3
- fastled/__version__.py,sha256=9FmVYi6n9qXRl6U6l1Xr9-rsxJ3ncEYDzHGxGM-sA_E,373
3
+ fastled/__version__.py,sha256=P02pxWlKUAylIYVEU3tL1Bf3RXb-VwgZjzO4VqZoHmc,373
4
4
  fastled/app.py,sha256=TFVn4qIRdt7dYbpDWudEHrhvD9pwyj9sIGXs4F26nhk,5880
5
- fastled/args.py,sha256=kucRGYpff_YKfmMpwWsJh6WIrvW_UPcNlZNFdw15z-Y,3475
5
+ fastled/args.py,sha256=sugtV0liHa8EnsTeXMMyeH0fO5DgeIzCbDcM5Pbr4k0,4010
6
6
  fastled/cli.py,sha256=drgR2AOxVrj3QEz58iiKscYAumbbin2vIV-k91VCOAA,561
7
7
  fastled/cli_test.py,sha256=W-1nODZrip_JU6BEbYhxOa4ckxduOsiX8zIoRkTyxv4,550
8
8
  fastled/cli_test_interactive.py,sha256=BjNhveZOk5aCffHbcrxPQQjWmAuj4ClVKKcKX5eY6yM,542
9
- fastled/client_server.py,sha256=2H3HBstR0kSKfLrz0LFbBIHy2WYBKc5VzhPxavhdS6o,20393
9
+ fastled/client_server.py,sha256=XH38jdEkkfizMrHYMDis5XEQqlG1XS7Bd-Yg1tQ1Zuk,20693
10
10
  fastled/compile_server.py,sha256=yQtwLOSKINO1CKD0NWxf-7YQKSatf9sF9RuqaWGOkCs,3038
11
11
  fastled/compile_server_impl.py,sha256=9vTGaDQ0W_g9Xsfy0gC3nJEc2g_pnXcF4VO2U3GLOVg,11982
12
12
  fastled/docker_manager.py,sha256=rkq39ZKrU6NHIyDa3mzs0Unb6o9oMeAwxhqiuHJU_RY,40291
@@ -14,9 +14,10 @@ fastled/filewatcher.py,sha256=gEcJJHTDJ1X3gKJzltmEBhixWGbZj2eJD7a4vwSvITQ,10036
14
14
  fastled/keyboard.py,sha256=UTAsqCn1UMYnB8YDzENiLTj4GeL45tYfEcO7_5fLFEg,3556
15
15
  fastled/keyz.py,sha256=LO-8m_7CpNDiZLM-FXhQ30f9gN1bUYz5lOsUPTIbI-c,4020
16
16
  fastled/live_client.py,sha256=yp_ujG92EHYpSedGOUteuG2nQvMKbp1GbUpgQ6nU4Dc,3083
17
- fastled/open_browser.py,sha256=DFyMrc1qic4Go7eLNPqMaLuMvTaE73NixdfSKV0yyp8,3709
18
- fastled/parse_args.py,sha256=Uu7aNaXtV9afE-z_zpINzykPnFewdN6Goiqgs6Rtt-c,11359
17
+ fastled/open_browser.py,sha256=So4znH-jjc-dheyRVGXrKcLj2vKgfGxCl_ECEfILY9g,5392
18
+ fastled/parse_args.py,sha256=hNyMeVlN2TX3MbhUkIAdVIeByHLoGFxuajXYmwK_7Fg,11948
19
19
  fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
20
+ fastled/playwright_browser.py,sha256=4Awad4ZIxWW-n9DMYwSGJBEzd6_7PA0ThQNms1kDxh4,16106
20
21
  fastled/print_filter.py,sha256=nc_rqYYdCUPinFycaK7fiQF5PG1up51pmJptR__QyAs,1499
21
22
  fastled/project_init.py,sha256=bBt4DwmW5hZkm9ICt9Qk-0Nr_0JQM7icCgH5Iv-bCQs,3984
22
23
  fastled/select_sketch_directory.py,sha256=-eudwCns3AKj4HuHtSkZAFwbnf005SNL07pOzs9VxnE,1383
@@ -37,9 +38,9 @@ fastled/site/build.py,sha256=2YKU_UWKlJdGnjdbAbaL0co6kceFMSTVYwH1KCmgPZA,13987
37
38
  fastled/site/examples.py,sha256=s6vj2zJc6BfKlnbwXr1QWY1mzuDBMt6j5MEBOWjO_U8,155
38
39
  fastled/test/can_run_local_docker_tests.py,sha256=LEuUbHctRhNNFWcvnz2kEGmjDJeXO4c3kNpizm3yVJs,400
39
40
  fastled/test/examples.py,sha256=GfaHeY1E8izBl6ZqDVjz--RHLyVR4NRnQ5pBesCFJFY,1673
40
- fastled-1.3.38.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
41
- fastled-1.3.38.dist-info/METADATA,sha256=j0Pj8VUdX6Tvsf6r5lbEfFF9dLO0FCJLHcm3Rw7feuo,30847
42
- fastled-1.3.38.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
43
- fastled-1.3.38.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
44
- fastled-1.3.38.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
45
- fastled-1.3.38.dist-info/RECORD,,
41
+ fastled-1.3.39.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
42
+ fastled-1.3.39.dist-info/METADATA,sha256=p2oZYp8GXF-76q_IB9VE41428EnWMBjhXracpjGkdzg,32198
43
+ fastled-1.3.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ fastled-1.3.39.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
45
+ fastled-1.3.39.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
46
+ fastled-1.3.39.dist-info/RECORD,,