@dionlarson/playwright-orchestrator-mysql 1.5.0 → 1.6.0

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.
@@ -14,6 +14,7 @@ export declare class MySQLAdapter extends Adapter {
14
14
  failTest(params: ResultTestParams): Promise<void>;
15
15
  saveTestRun({ runId, testRun, args, historyWindow }: SaveTestRunParams): Promise<void>;
16
16
  initialize(): Promise<void>;
17
+ private addTitleColumnIfNeeded;
17
18
  startShard(runId: string): Promise<TestRunConfig>;
18
19
  finishShard(runId: string): Promise<void>;
19
20
  dispose(): Promise<void>;
@@ -42,31 +42,27 @@ export class MySQLAdapter extends Adapter {
42
42
  const client = await this.pool.getConnection();
43
43
  try {
44
44
  await client.beginTransaction();
45
- const [result] = await client.query(`SET @order_num = (SELECT order_num FROM ??
46
- WHERE run_id = UUID_TO_BIN(?) AND status = ?
47
- ORDER BY order_num
48
- LIMIT 1
49
- FOR UPDATE SKIP LOCKED);
50
- UPDATE ??
51
- SET status = ?, updated = CURRENT_TIMESTAMP
52
- WHERE run_id = UUID_TO_BIN(?) AND order_num = @order_num;
53
-
54
- SELECT * FROM ??
55
- WHERE run_id = UUID_TO_BIN(?) AND order_num = @order_num`, [
56
- this.testsTable,
57
- runId,
58
- TestStatus.Ready,
45
+ // Use UPDATE ... ORDER BY ... LIMIT 1 for truly atomic claim.
46
+ // This locks exactly one row during the UPDATE, preventing race conditions.
47
+ // Store the claimed order_num in a session variable for the subsequent SELECT.
48
+ const [result] = await client.query(`UPDATE ?? SET status = ?, updated = CURRENT_TIMESTAMP, order_num = (@claimed_order := order_num)
49
+ WHERE run_id = UUID_TO_BIN(?) AND status = ?
50
+ ORDER BY order_num
51
+ LIMIT 1;
52
+
53
+ SELECT * FROM ?? WHERE run_id = UUID_TO_BIN(?) AND order_num = @claimed_order`, [
59
54
  this.testsTable,
60
55
  TestStatus.Ongoing,
61
56
  runId,
57
+ TestStatus.Ready,
62
58
  this.testsTable,
63
59
  runId,
64
60
  ]);
65
61
  await client.commit();
66
- if (result[2].length === 0)
62
+ if (result[1].length === 0)
67
63
  return undefined;
68
- const { file, line, pos, project, timeout, order_num } = result[2][0];
69
- return { file, position: `${line}:${pos}`, project, timeout, order: order_num };
64
+ const { file, line, pos, project, timeout, order_num, title } = result[1][0];
65
+ return { file, position: `${line}:${pos}`, project, timeout, order: order_num, title };
70
66
  }
71
67
  catch (e) {
72
68
  await client.rollback();
@@ -86,9 +82,9 @@ export class MySQLAdapter extends Adapter {
86
82
  let tests = this.transformTestRunToItems(testRun.testRun);
87
83
  const testInfos = await this.loadTestInfos(tests);
88
84
  tests = this.sortTests(tests, testInfos, { historyWindow });
89
- const testValues = tests.map(({ position, order, file, project, timeout }) => {
85
+ const testValues = tests.map(({ position, order, file, project, timeout, title }) => {
90
86
  const [line, character] = position.split(':');
91
- return [runId, order, file, +line, +character, project, timeout];
87
+ return [runId, order, file, +line, +character, project, timeout, title];
92
88
  });
93
89
  const values = [
94
90
  this.configTable,
@@ -101,8 +97,8 @@ export class MySQLAdapter extends Adapter {
101
97
  await this.pool.query({
102
98
  sql: `
103
99
  INSERT INTO ?? (id, status, config) VALUES (UUID_TO_BIN(?), ?, ?);
104
- INSERT INTO ?? (run_id, order_num, file, line, pos, project, timeout) VALUES ${testValues
105
- .map(() => '(UUID_TO_BIN(?), ?, ?, ?, ?, ?, ?)')
100
+ INSERT INTO ?? (run_id, order_num, file, line, pos, project, timeout, title) VALUES ${testValues
101
+ .map(() => '(UUID_TO_BIN(?), ?, ?, ?, ?, ?, ?, ?)')
106
102
  .join(', ')}`,
107
103
  values: values,
108
104
  });
@@ -124,6 +120,7 @@ export class MySQLAdapter extends Adapter {
124
120
  pos INT NOT NULL,
125
121
  project TEXT NOT NULL,
126
122
  timeout INT NOT NULL,
123
+ title TEXT NOT NULL DEFAULT '',
127
124
  updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
128
125
  report JSON,
129
126
  PRIMARY KEY (run_id, order_num),
@@ -156,6 +153,21 @@ export class MySQLAdapter extends Adapter {
156
153
  this.testInfoTable,
157
154
  ],
158
155
  });
156
+ // Migration: add title column if it doesn't exist (for existing databases)
157
+ await this.addTitleColumnIfNeeded();
158
+ }
159
+ async addTitleColumnIfNeeded() {
160
+ try {
161
+ await this.pool.query({
162
+ sql: `ALTER TABLE ?? ADD COLUMN title TEXT NOT NULL DEFAULT ''`,
163
+ values: [this.testsTable],
164
+ });
165
+ }
166
+ catch (e) {
167
+ // Error code 1060 = Duplicate column name (column already exists)
168
+ if (e.errno !== 1060)
169
+ throw e;
170
+ }
159
171
  }
160
172
  async startShard(runId) {
161
173
  const client = await this.pool.getConnection();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dionlarson/playwright-orchestrator-mysql",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "keywords": [],
5
5
  "author": "Rostyslav Kudrevatykh",
6
6
  "license": "Apache-2.0",