diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index e3d5a638131c90ef70213c0191c34f0175c2b784..42fe6ae2b8244345221086e62ce63b949b0e9507 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -1,22 +1,29 @@
 name: Tests
-on: [push, pull_request]
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+  workflow_dispatch:
+
 jobs:
   build:
     runs-on: ubuntu-20.04
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9, '3.10', 3.11-dev]
+        python-version: [3.6, 3.7, 3.8, 3.9, '3.10', 3.11, '3.12-dev']
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
     - name: run tests
       env:
         STACK_DATA_SLOW_TESTS: 1
       run: |
-        pip install -U pip
+        pip install --upgrade pip
         pip install --upgrade coveralls setuptools setuptools_scm pep517
         pip install .[tests]
         coverage run --source stack_data -m pytest
diff --git a/setup.cfg b/setup.cfg
index 05f3ddac28c780df1ae323fe2e072d76c409e77a..11cbe80eb01096ad35f660be42e5685b138a4af8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,6 +16,7 @@ classifiers =
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
+    Programming Language :: Python :: 3.12
     License :: OSI Approved :: MIT License
     Operating System :: OS Independent
     Topic :: Software Development :: Debuggers
diff --git a/stack_data/utils.py b/stack_data/utils.py
index 78ce2d60a400b0acb3753ca038412ed1b66d0199..ad8cd38dc46b41155cabf4ff264d6d4f4beef8ea 100644
--- a/stack_data/utils.py
+++ b/stack_data/utils.py
@@ -92,12 +92,13 @@ def is_frame(frame_or_tb: Union[FrameType, TracebackType]) -> bool:
 
 
 def iter_stack(frame_or_tb: Union[FrameType, TracebackType]) -> Iterator[Union[FrameType, TracebackType]]:
-    while frame_or_tb:
-        yield frame_or_tb
-        if is_frame(frame_or_tb):
-            frame_or_tb = frame_or_tb.f_back
+    current: Union[FrameType, TracebackType, None] = frame_or_tb
+    while current:
+        yield current
+        if is_frame(current):
+            current = current.f_back
         else:
-            frame_or_tb = frame_or_tb.tb_next
+            current = current.tb_next
 
 
 def frame_and_lineno(frame_or_tb: Union[FrameType, TracebackType]) -> Tuple[FrameType, int]:
diff --git a/tests/__init__.py b/tests/__init__.py
index fe281111c72be0728401c7f737866454cd6b88c1..e61d09f772b20e4fed930d68a2c0d93c55c2497e 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,7 +1,10 @@
 import os
 
 import pyximport
-from typeguard.importhook import install_import_hook
+try:
+    from typeguard import install_import_hook
+except ImportError:
+    from typeguard.importhook import install_import_hook
 
 pyximport.install(language_level=3)
 
diff --git a/tests/golden_files/pygmented.txt b/tests/golden_files/pygmented.txt
index 920dabc85f847d8c522c639533071beb9490dd36..c82f142cb629689da9f95b7a01cfa7d602adc583 100644
--- a/tests/golden_files/pygmented.txt
+++ b/tests/golden_files/pygmented.txt
@@ -1,11 +1,11 @@
 Traceback (most recent call last):
  File "formatter_example.py", line 21, in foo
-       9 | x = 1
-      10 | lst = (
+       9 | x = 1
+      10 | lst = (
       11 |         [
       12 |             x,
 (...)
-      18 |         + []
+      18 |         + []
       19 | )
       20 | try:
 -->   21 |     return int(str(lst))
@@ -19,7 +19,7 @@ Traceback (most recent call last):
       21 |     return int(str(lst))
       22 | except:
       23 |     try:
--->   24 |         return 1 / 0
+-->   24 |         return 1 / 0
       25 |     except Exception as e:
 ZeroDivisionError: division by zero
 
@@ -31,24 +31,24 @@ Traceback (most recent call last):
 -->   30 |     exec("foo()")
  File "<string>", line 1, in <module>
  File "formatter_example.py", line 8, in foo
-       6 | def foo(n=5):
-       7 |     if n > 0:
--->    8 |         return foo(n - 1)
-       9 |     x = 1
+       6 | def foo(n=5):
+       7 |     if n > 0:
+-->    8 |         return foo(n - 1)
+       9 |     x = 1
  File "formatter_example.py", line 8, in foo
-       6 | def foo(n=5):
-       7 |     if n > 0:
--->    8 |         return foo(n - 1)
-       9 |     x = 1
+       6 | def foo(n=5):
+       7 |     if n > 0:
+-->    8 |         return foo(n - 1)
+       9 |     x = 1
     [... skipping similar frames: foo at line 8 (2 times)]
  File "formatter_example.py", line 8, in foo
-       6 | def foo(n=5):
-       7 |     if n > 0:
--->    8 |         return foo(n - 1)
-       9 |     x = 1
+       6 | def foo(n=5):
+       7 |     if n > 0:
+-->    8 |         return foo(n - 1)
+       9 |     x = 1
  File "formatter_example.py", line 26, in foo
       23 | try:
-      24 |     return 1 / 0
+      24 |     return 1 / 0
       25 | except Exception as e:
--->   26 |     raise TypeError from e
+-->   26 |     raise TypeError from e
 TypeError
diff --git a/tests/golden_files/serialize.json b/tests/golden_files/serialize.json
index 94a190ac36548e64aba651ab2b3779cdf9464f70..f084d185a15ce07acfd67b008f655961bf471ebd 100644
--- a/tests/golden_files/serialize.json
+++ b/tests/golden_files/serialize.json
@@ -582,13 +582,13 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 10,
-                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mlst\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m(\u001b[39m"
+                            "text": "\u001b[38;5;15m\u001b[39m\u001b[38;5;15mlst\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m(\u001b[39m"
                         },
                         {
                             "type": "line",
@@ -609,7 +609,7 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 18,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;197m+\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m[\u001b[39m\u001b[38;5;15m]\u001b[39m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;204m+\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15m[\u001b[39m\u001b[38;5;15m]\u001b[39m"
                         },
                         {
                             "type": "line",
@@ -724,7 +724,7 @@
                             "type": "line",
                             "is_current": true,
                             "lineno": 24,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m/\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m0\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m/\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m0\u001b[39;49m"
                         },
                         {
                             "type": "line",
@@ -832,25 +832,25 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 6,
-                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 7,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": true,
                             "lineno": 8,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         }
                     ],
                     "variables": [
@@ -878,25 +878,25 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 6,
-                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 7,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": true,
                             "lineno": 8,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         }
                     ],
                     "variables": [
@@ -934,25 +934,25 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 6,
-                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;81mdef\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mfoo\u001b[39m\u001b[38;5;15m(\u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;141m5\u001b[39m\u001b[38;5;15m)\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 7,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mif\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15mn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m>\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m\u001b[38;5;15m:\u001b[39m"
                         },
                         {
                             "type": "line",
                             "is_current": true,
                             "lineno": 8,
-                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;197;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
+                            "text": "\u001b[38;5;15m        \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15;48;5;24mfoo\u001b[39;49m\u001b[38;5;15;48;5;24m(\u001b[39;49m\u001b[38;5;15;48;5;24mn\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;204;48;5;24m-\u001b[39;49m\u001b[38;5;15;48;5;24m \u001b[39;49m\u001b[38;5;141;48;5;24m1\u001b[39;49m\u001b[38;5;15;48;5;24m)\u001b[39;49m"
                         },
                         {
                             "type": "line",
                             "is_current": false,
                             "lineno": 9,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;15mx\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m=\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m"
                         }
                     ],
                     "variables": [
@@ -986,7 +986,7 @@
                             "type": "line",
                             "is_current": false,
                             "lineno": 24,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197m/\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mreturn\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m1\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204m/\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;141m0\u001b[39m"
                         },
                         {
                             "type": "line",
@@ -998,7 +998,7 @@
                             "type": "line",
                             "is_current": true,
                             "lineno": 26,
-                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mraise\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mTypeError\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;197mfrom\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15me\u001b[39m"
+                            "text": "\u001b[38;5;15m    \u001b[39m\u001b[38;5;81mraise\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;148mTypeError\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;204mfrom\u001b[39m\u001b[38;5;15m \u001b[39m\u001b[38;5;15me\u001b[39m"
                         }
                     ],
                     "variables": [
diff --git a/tests/test_core.py b/tests/test_core.py
index 52ad3aeac1aa8f1a33cc4565d49e9b6b39984404..b0ecdc2f21a1b19d09f7cf31863d74f46667cc94 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -484,7 +484,7 @@ def sys_modules_sources():
     for module in list(sys.modules.values()):
         try:
             filename = inspect.getsourcefile(module)
-        except TypeError:
+        except (TypeError, AttributeError):
             continue
 
         if not filename:
@@ -630,7 +630,7 @@ x = 1
 """
 
 
-@pytest.mark.skipif(pygments_version < (2, 12), reason="Different output in older Pygments")
+@pytest.mark.skipif(pygments_version < (2, 14), reason="Different output in older Pygments")
 def test_pygments_example():
     from .samples.pygments_example import bar
     result = bar()
@@ -670,32 +670,32 @@ Terminal256Formatter <class \'stack_data.core.style_with_executing_node.<locals>
 
 TerminalFormatter native:
 
-  13 | \x1b[34mdef\x1b[39;49;00m \x1b[32mbar\x1b[39;49;00m():
-  14 |     x = \x1b[34m1\x1b[39;49;00m
-  15 |     \x1b[36mstr\x1b[39;49;00m(x)
-  17 |     \x1b[90m@deco\x1b[39;49;00m
-  18 |     \x1b[34mdef\x1b[39;49;00m \x1b[32mfoo\x1b[39;49;00m():
-  19 |         \x1b[34mpass\x1b[39;49;00m
+  13 | \x1b[34mdef\x1b[39;49;00m \x1b[32mbar\x1b[39;49;00m():\x1b[37m\x1b[39;49;00m
+  14 |     x = \x1b[34m1\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+  15 |     \x1b[36mstr\x1b[39;49;00m(x)\x1b[37m\x1b[39;49;00m
+  17 |     \x1b[90m@deco\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+  18 |     \x1b[34mdef\x1b[39;49;00m \x1b[32mfoo\x1b[39;49;00m():\x1b[37m\x1b[39;49;00m
+  19 |         \x1b[34mpass\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
 -----
-  25 | \x1b[34mdef\x1b[39;49;00m \x1b[32mdeco\x1b[39;49;00m(f):
-  26 |     f.result = print_stack()
-  27 |     \x1b[34mreturn\x1b[39;49;00m f
+  25 | \x1b[34mdef\x1b[39;49;00m \x1b[32mdeco\x1b[39;49;00m(f):\x1b[37m\x1b[39;49;00m
+  26 |     f.result = print_stack()\x1b[37m\x1b[39;49;00m
+  27 |     \x1b[34mreturn\x1b[39;49;00m f\x1b[37m\x1b[39;49;00m
 -----
 
 ====================
 
 TerminalFormatter <class \'stack_data.core.style_with_executing_node.<locals>.NewStyle\'>:
 
-  13 | \x1b[34mdef\x1b[39;49;00m \x1b[32mbar\x1b[39;49;00m():
-  14 |     x = \x1b[34m1\x1b[39;49;00m
-  15 |     \x1b[36mstr\x1b[39;49;00m(x)
-  17 |     \x1b[90m@deco\x1b[39;49;00m
-  18 |     \x1b[34mdef\x1b[39;49;00m \x1b[32mfoo\x1b[39;49;00m():
-  19 |         \x1b[34mpass\x1b[39;49;00m
+  13 | \x1b[34mdef\x1b[39;49;00m \x1b[32mbar\x1b[39;49;00m():\x1b[37m\x1b[39;49;00m
+  14 |     x = \x1b[34m1\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+  15 |     \x1b[36mstr\x1b[39;49;00m(x)\x1b[37m\x1b[39;49;00m
+  17 |     \x1b[90m@deco\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
+  18 |     \x1b[34mdef\x1b[39;49;00m \x1b[32mfoo\x1b[39;49;00m():\x1b[37m\x1b[39;49;00m
+  19 |         \x1b[34mpass\x1b[39;49;00m\x1b[37m\x1b[39;49;00m
 -----
-  25 | \x1b[34mdef\x1b[39;49;00m \x1b[32mdeco\x1b[39;49;00m(f):
-  26 |     f.result = print_stack()
-  27 |     \x1b[34mreturn\x1b[39;49;00m f
+  25 | \x1b[34mdef\x1b[39;49;00m \x1b[32mdeco\x1b[39;49;00m(f):\x1b[37m\x1b[39;49;00m
+  26 |     f.result = print_stack()\x1b[37m\x1b[39;49;00m
+  27 |     \x1b[34mreturn\x1b[39;49;00m f\x1b[37m\x1b[39;49;00m
 -----
 
 ====================
@@ -753,9 +753,9 @@ HtmlFormatter <class \'stack_data.core.style_with_executing_node.<locals>.NewSty
   13 | <span class="k">def</span> <span class="nf">bar</span><span class="p">():</span>
   14 |     <span class="n">x</span> <span class="o">=</span> <span class="mi">1</span>
   15 |     <span class="nb">str</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
-  17 | <span class=" -ExecutingNode">    </span><span class="nd nd-ExecutingNode">@deco</span><span class=" -ExecutingNode"></span>
-  18 | <span class=" -ExecutingNode">    </span><span class="k k-ExecutingNode">def</span><span class=" -ExecutingNode"> </span><span class="nf nf-ExecutingNode">foo</span><span class="p p-ExecutingNode">():</span><span class=" -ExecutingNode"></span>
-  19 | <span class=" -ExecutingNode">        </span><span class="k k-ExecutingNode">pass</span><span class=" -ExecutingNode"></span>
+  17 | <span class=" -ExecutingNode">    </span><span class="nd nd-ExecutingNode">@deco</span>
+  18 | <span class=" -ExecutingNode">    </span><span class="k k-ExecutingNode">def</span><span class=" -ExecutingNode"> </span><span class="nf nf-ExecutingNode">foo</span><span class="p p-ExecutingNode">():</span>
+  19 | <span class=" -ExecutingNode">        </span><span class="k k-ExecutingNode">pass</span>
 -----
   25 | <span class="k">def</span> <span class="nf">deco</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
   26 |     <span class="n">f</span><span class="o">.</span><span class="n">result</span> <span class="o">=</span> <span class="n n-ExecutingNode">print_stack</span><span class="p p-ExecutingNode">()</span>
diff --git a/tests/test_formatter.py b/tests/test_formatter.py
index 31862f82e44ac409d3f06be641e897c462a34a5a..2ef9ecffff253f104a3f0f7c041f5e05187ea6e9 100644
--- a/tests/test_formatter.py
+++ b/tests/test_formatter.py
@@ -3,8 +3,9 @@ import re
 import sys
 from contextlib import contextmanager
 
-import pytest
 import pygments
+import pytest
+from asttokens.util import fstring_positions_work
 
 from stack_data import Formatter, FrameInfo, Options, BlankLines
 from tests.utils import compare_to_file
@@ -82,8 +83,7 @@ def test_example(capsys):
 
     if sys.version_info[:2] < (3, 8):
         f_string_suffix = 'old'
-    elif sys.version_info[:2] == (3, 8):
-        # lineno/col_offset in f-strings cannot be trusted in 3.8
+    elif not fstring_positions_work():
         f_string_suffix = '3.8'
     else:
         f_string_suffix = 'new'
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 807872d508b31f02269022ca94a1e8023562d608..bc8acca3f79371e5f8f02e28f4b7a86cdb1cf766 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -39,4 +39,4 @@ def test_example():
         )
 
 
-    compare_to_file_json(result, "serialize")
+    compare_to_file_json(result, "serialize", pygmented=True)
diff --git a/tests/utils.py b/tests/utils.py
index 7ba00e2dfd16e5eb2d4b950c62088d6432906591..15000868c73f52bd648c217557309ab9b2ff45be 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,9 +1,19 @@
 import os
 
+import pygments
 from littleutils import string_to_file, file_to_string, json_to_file, file_to_json
 
 
+def parse_version(version: str):
+    return tuple(int(x) for x in version.split("."))
+
+
+old_pygments = parse_version(pygments.__version__) < (2, 16, 1)
+
+
 def compare_to_file(text, name):
+    if old_pygments and "pygment" in name:
+        return
     filename = os.path.join(
         os.path.dirname(__file__),
         'golden_files',
@@ -16,7 +26,9 @@ def compare_to_file(text, name):
         assert text == expected_output
 
 
-def compare_to_file_json(data, name):
+def compare_to_file_json(data, name, *, pygmented):
+    if old_pygments and pygmented:
+        return
     filename = os.path.join(
         os.path.dirname(__file__),
         'golden_files',
diff --git a/tox.ini b/tox.ini
index b613d58bdc4d4d81b796abf672d740df4c65c70d..e5c0a6661ccc46434b2e1b6447592cc65a494b17 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,9 @@
 [tox]
-envlist = py{36,37,38,39,310,311}
+envlist = py{36,37,38,39,310,311,312}
 
 [testenv]
 commands = pytest {posargs}
 extras = tests
 passenv =
     STACK_DATA_SLOW_TESTS
+    FIX_STACK_DATA_TESTS