Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 62 additions & 51 deletions .github/workflows/migration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ env:
PG_DB: langflow
STATE_FILE: /tmp/migration-test-state.json
REPORT_FILE: /tmp/migration-report.md
# Fixed key shared by both containers so credentials encrypted by latest
# can be decrypted by nightly after DB migration.
LANGFLOW_SECRET_KEY: "migration-test-fixed-secret-32chars!!"
LANGFLOW_DATABASE_URL: "postgresql://langflow:langflow_test_pw@localhost:5432/langflow"
LANGFLOW_AUTO_LOGIN: "true"
LANGFLOW_SKIP_AUTH_AUTO_LOGIN: "true"
LANGFLOW_SUPERUSER: "langflow"
LANGFLOW_SUPERUSER_PASSWORD: "langflow123"
LANGFLOW_STORE: "false"

permissions:
contents: read
Expand Down Expand Up @@ -49,41 +52,41 @@ jobs:
with:
python-version: "3.12"

- name: Install dependencies
- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Install test dependencies
run: |
pip install requests playwright pytest pytest-playwright
playwright install chromium --with-deps

- name: Generate ephemeral Fernet key for this run
- name: Create virtual environment
run: uv venv .venv
Comment on lines +63 to +64

- name: Generate ephemeral secret key for this run
run: |
python - <<'PY'
import base64
import os

import base64, os
key = base64.urlsafe_b64encode(os.urandom(32)).decode()
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as github_env:
github_env.write(f"LANGFLOW_SECRET_KEY={key}\n")
with open(os.environ["GITHUB_ENV"], "a") as f:
f.write(f"LANGFLOW_SECRET_KEY={key}\n")
PY

# ── Phase 1: Langflow Latest ──────────────────────────────
- name: Pull Langflow latest
- name: Install Langflow latest
run: |
docker pull langflowai/langflow:latest
docker inspect langflowai/langflow:latest --format='{{index .RepoDigests 0}}' > /tmp/latest-digest.txt
echo "Latest digest: $(cat /tmp/latest-digest.txt)"
uv pip install langflow
LATEST_VERSION=$(uv pip show langflow | grep ^Version | awk '{print $2}')
echo "LATEST_VERSION=$LATEST_VERSION" >> "$GITHUB_ENV"
echo "langflow==$LATEST_VERSION" > /tmp/latest-digest.txt
echo "Installed Langflow $LATEST_VERSION (latest stable)"

- name: Start Langflow latest
run: |
docker run -d --name langflow \
--network host \
-e LANGFLOW_DATABASE_URL="postgresql://$PG_USER:$PG_PASSWORD@localhost:5432/$PG_DB" \
-e LANGFLOW_AUTO_LOGIN=true \
-e LANGFLOW_SKIP_AUTH_AUTO_LOGIN=true \
-e LANGFLOW_SUPERUSER=langflow \
-e LANGFLOW_SUPERUSER_PASSWORD=langflow123 \
-e LANGFLOW_STORE=false \
-e LANGFLOW_SECRET_KEY="$LANGFLOW_SECRET_KEY" \
langflowai/langflow:latest
uv run langflow run --host 0.0.0.0 --port "$LANGFLOW_PORT" \
> /tmp/langflow-latest.log 2>&1 &
echo $! > /tmp/langflow.pid
echo "Langflow PID: $(cat /tmp/langflow.pid)"

- name: Wait for Langflow latest
run: python tests/github-workflows/migration/wait_for_langflow.py
Expand All @@ -93,41 +96,49 @@ jobs:
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

- name: Save latest container logs
if: always()
run: docker logs langflow > /tmp/langflow-latest.log 2>&1 || true
- name: Stop Langflow latest
run: |
kill "$(cat /tmp/langflow.pid)" 2>/dev/null || true
sleep 3

# ── Phase 2: Upgrade to Nightly ───────────────────────────
- name: Stop Langflow latest
run: docker stop langflow && docker rm langflow
- name: Resolve nightly version from PyPI
run: |
NIGHTLY_VERSION=$(python - <<'PY'
import urllib.request, json
data = json.loads(urllib.request.urlopen("https://pypi.org/pypi/langflow/json").read())
pre = [
v for v in data["releases"]
if data["releases"][v] and any(c in v for c in ("dev", "a", "b", "rc"))
]
pre.sort(key=lambda v: data["releases"][v][0]["upload_time"])
print(pre[-1] if pre else "")
Comment on lines +108 to +115
PY
)
if [ -z "$NIGHTLY_VERSION" ]; then
echo "ERROR: Could not determine nightly version from PyPI"
exit 1
fi
echo "NIGHTLY_VERSION=$NIGHTLY_VERSION" >> "$GITHUB_ENV"
echo "langflow==$NIGHTLY_VERSION" > /tmp/nightly-digest.txt
echo "Nightly version: $NIGHTLY_VERSION"

- name: Pull Langflow nightly
- name: Install Langflow nightly
run: |
docker pull langflowai/langflow-nightly:latest
docker inspect langflowai/langflow-nightly:latest --format='{{index .RepoDigests 0}}' > /tmp/nightly-digest.txt
echo "Nightly digest: $(cat /tmp/nightly-digest.txt)"
uv pip install "langflow==$NIGHTLY_VERSION"
echo "Installed Langflow $NIGHTLY_VERSION (nightly)"

- name: Start Langflow nightly (same database)
run: |
docker run -d --name langflow \
--network host \
-e LANGFLOW_DATABASE_URL="postgresql://$PG_USER:$PG_PASSWORD@localhost:5432/$PG_DB" \
-e LANGFLOW_AUTO_LOGIN=true \
-e LANGFLOW_SKIP_AUTH_AUTO_LOGIN=true \
-e LANGFLOW_SUPERUSER=langflow \
-e LANGFLOW_SUPERUSER_PASSWORD=langflow123 \
-e LANGFLOW_STORE=false \
-e LANGFLOW_SECRET_KEY="$LANGFLOW_SECRET_KEY" \
langflowai/langflow-nightly:latest
uv run langflow run --host 0.0.0.0 --port "$LANGFLOW_PORT" \
> /tmp/langflow-nightly.log 2>&1 &
echo $! > /tmp/langflow.pid
echo "Langflow PID: $(cat /tmp/langflow.pid)"

- name: Wait for Langflow nightly (includes migration)
id: nightly_start
run: python tests/github-workflows/migration/wait_for_langflow.py --timeout 180

- name: Save nightly container logs
if: always()
run: docker logs langflow > /tmp/langflow-nightly.log 2>&1 || true

# ── Phase 3: Verify Migration ─────────────────────────────
- name: Verify migration via API
run: python tests/github-workflows/migration/verify_migration_api.py
Expand All @@ -140,6 +151,10 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

# ── Phase 4: Report ───────────────────────────────────────
- name: Stop Langflow nightly
if: always()
run: kill "$(cat /tmp/langflow.pid)" 2>/dev/null || true

- name: Generate report
if: always()
run: python tests/github-workflows/migration/generate_report.py
Expand All @@ -162,10 +177,6 @@ jobs:
/tmp/latest-digest.txt
/tmp/nightly-digest.txt

- name: Cleanup containers
if: always()
run: docker stop langflow 2>/dev/null; docker rm langflow 2>/dev/null; true

- name: Close issue on success
if: success()
uses: actions/github-script@v7
Expand Down
16 changes: 8 additions & 8 deletions tests/github-workflows/migration/test_ui_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ def test_01_open_flow(self):
self.page.goto(f"{self.base_url}/flow/{self.flow_id}", wait_until="networkidle")
self.page.wait_for_timeout(5000)

# Verify the flow editor loaded — use a broad selector resilient to
# ReactFlow version changes (class prefix match covers react-flow,
# react-flow__renderer, react-flow__pane, etc.)
canvas = self.page.locator("[class*='react-flow'], [data-testid='rf__wrapper']")
if canvas.count() == 0:
# Fallback: check for any generic node card present in the flow editor
canvas = self.page.locator(".generic-node, [data-testid*='node'], .node-card")
expect(canvas.first).to_be_visible(timeout=20_000)
# Verify the flow editor loaded: check the URL still contains the flow_id
# (not redirected to home/login) and that the page has interactive content.
# Avoids coupling to ReactFlow's internal CSS class names which change across versions.
expect(self.page).to_have_url(re.compile(self.flow_id), timeout=20_000)

# Also verify the React app div is in the DOM (not a blank/loading screen).
# Uses state="attached" to avoid matching the invisible <noscript> sibling.
self.page.wait_for_selector("body > div", state="attached", timeout=10_000)
Comment on lines +60 to +67

self.results["steps"]["open_flow"] = {"status": "pass"}
self._save_results()
Expand Down
Loading